模板&策略模式
实现姿势
我们会从简单到复杂,讲解代码正确的实现姿势,分别为最 Low 方式、常规方式、模板模式和策略模式。
最 Low 方式
假如现在有 3 个奥特曼,都喜欢 “训练,飞翔,打怪兽”
public class OldTiga {
public void everyDay() {
System.out.println("迪迦==>飞翔");
System.out.println("迪迦==>训练");
System.out.println("迪迦==>光线打怪兽");
}
}
public class OldDeNa {
public void everyDay() {
System.out.println("戴拿==>训练");
System.out.println("戴拿==>飞翔");
System.out.println("戴拿==>体术打怪兽");
}
}
public class OldMonsters {
public void everyDay() {
System.out.println("怪兽==>飞翔");
System.out.println("怪兽==>训练");
System.out.println("怪兽==>我**就是怪兽,谁敢打我");
}
}
public class OldLandLight {
public static void main(String[] args) {
OldTiga oldTiga = new OldTiga();
oldTiga.everyDay();
OldDeNa oldDeNa = new OldDeNa();
oldDeNa.everyDay();
OldMonsters oldMonsters = new OldMonsters();
oldMonsters.everyDay();
}
}
测试:
这种方式是大家写代码时,最容易使用的方式,上手简单,也容易理解,目前看项目中陈旧的代码,经常能找到它们的影子,下面我们看怎么一步步将其进行重构。
常规方式
“训练,飞翔,打怪兽” 其实都是独立的行为,为了不相互影响,我们可以通过函数简单进行封装:
public class RTiga {
public void fly(){
System.out.println("迪迦==>飞翔");
}
public void training(){
System.out.println("迪迦==>训练");
}
public void fightMonsters(){
System.out.println("迪迦==>光线打怪兽");
}
}
public class RDeNa {
public void fly(){
System.out.println("戴拿==>飞翔");
}
public void training(){
System.out.println("戴拿==>训练");
}
public void fightMonsters(){
System.out.println("戴拿==>光线打怪兽");
}
}
public class RMonsters {
public void fly(){
System.out.println("怪兽==>飞翔");
}
public void training(){
System.out.println("怪兽==>训练");
}
public void fightMonsters(){
System.out.println("怪兽==>我**就是怪兽,谁敢打我");
}
}
public class RLandLight {
public static void main(String[] args) {
RTiga rTiga = new RTiga();
rTiga.fly();
rTiga.training();
rTiga.fightMonsters();
RDeNa rDeNa = new RDeNa();
rDeNa.fly();
rDeNa.training();
rDeNa.fightMonsters();
RMonsters rMonsters = new RMonsters();
rMonsters.fly();
rMonsters.training();
rMonsters.fightMonsters();
}
}
测试结果:
模板模式
定义:一个抽象类公开定义了执行它的方法的方式/模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行,属于行为型模式。
这 3 个奥特曼,因为 “训练,飞翔” 都一样,所以我们可以直接实现出来,但是他们 “打怪兽” 的方式不同,所以封装成抽象方法,需要每个奥特曼单独去实现 “打怪兽” 的方式。
最后再新增一个方法 everyDo(),固定每天的执行流程:
定义抽象奥特曼类
public abstract class Ultraman {
public void fly(String name){
System.out.println(name+"==>飞翔");
}
public void training(String name){
System.out.println(name+"==>训练");
}
abstract void fightMonsters(String name);
public void everyDo(String name){
this.fly(name);
this.training(name);
this.fightMonsters(name);
}
}
每个不同的奥特曼实现自己的打怪兽方法
@Data
@Accessors(chain = true)
public class Tiga extends Ultraman {
private String name;
@Override
void fightMonsters(String name) {
System.out.println(name+"==>光线打怪兽");
}
}
@Data
@Accessors(chain = true)
public class DeNa extends Ultraman {
private String name;
@Override
void fightMonsters(String name) {
System.out.println(name+"==>体术打怪兽");
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Monsters extends Ultraman {
private String name;
@Override
void fightMonsters(String name) {
System.out.println(name+"==>我**就是怪兽,谁敢打我");
}
}
在光之国调用
public class LandLight {
public static void main(String[] args) {
Tiga tiga = new Tiga().setName("迪迦");
tiga.everyDo(tiga.getName());
DeNa deNa = new DeNa().setName("戴拿");
deNa.everyDo(deNa.getName());
Monsters monsters = new Monsters("怪兽");
monsters.everyDo(monsters.getName());
}
}
测试结果:
策略模式
定义:一个类的行为或其算法可以在运行时更改,即我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象,策略对象改变 context 对象的执行算法,属于行为型模式。
还是先定义奥特曼抽象类
public abstract class SUltraman {
public void fly(String name){
System.out.println(name+"==>飞翔");
}
public void training(String name){
System.out.println(name+"==>训练");
}
abstract void fightMonsters(String name);
}
每个奥特曼实现自己的打怪兽方法
@Data
@Accessors(chain = true)
public class STiga extends SUltraman {
@Override
void fightMonsters(String name) {
System.out.println(name+"==>光线打怪兽");
}
}
@Data
@Accessors(chain = true)
public class SDeNa extends SUltraman {
@Override
void fightMonsters(String name) {
System.out.println(name+"==>体术打怪兽");
}
}
@Data
public class SMonsters extends SUltraman {
@Override
void fightMonsters(String name) {
System.out.println(name+"==>我**就是怪兽,谁敢打我");
}
}
这里就是策略模式的重点,我们再看一下策略模式的定义 “我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象”,这里只需要关注策略的执行行为,而不需要关注是谁来执行的,只重视行为本身,而不去重视执行行为的操作者,那么该 contex 对象如下:
public class BeHaviorContext {
private SUltraman ultraman;
private String name;
public BeHaviorContext(SUltraman newUltraman,String newName){
name = newName;
ultraman = newUltraman;
}
public void setUltraman(SUltraman newUltraman,String newName){
name = newName;
ultraman = newUltraman;
}
public void everyDo(){
ultraman.fly(this.name);
ultraman.training(this.name);
ultraman.fightMonsters(this.name);
}
}
光之国的执行测试:
public class SLandLight {
public static void main(String[] args) {
BeHaviorContext beHaviorContext = new BeHaviorContext(new STiga(),"迪迦");
beHaviorContext.everyDo();
//重新设置新的策略对象
beHaviorContext.setUltraman(new SDeNa(),"戴拿");
beHaviorContext.everyDo();
beHaviorContext.setUltraman(new SMonsters(),"怪兽");
beHaviorContext.everyDo();
}
}
执行结果:
我们可以通过给 behaviorContext 传递不同的策略对象,然后来约定 everyDo() 的调用方式。
其实这个示例,有点把策略模式讲复杂了,因为纯粹的策略模式,3 个奥特曼只有 fightMonsteres() 方法不同,所以可以把 fightMonsteres() 理解为不同的算法即可。
之所以引入 everyDo(),是因为实际的项目场景中,会经常这么使用,也就是把这个变化的算法 fightMonsteres(),包装到具体的执行流程里面,所以策略模式就看起来没有那么直观,但是核心思想是一样的。
相似:
策略和模板方法模式都可以用来满足开闭原则,使得软件模块在不改变代码的情况下易于扩展;
两种模式都表示通用 function 与该 function 的详细实现的分离,不过它们所提供的粒度有一些差异。
差异:
策略模式:
它基于接口;
客户和策略之间的耦合更加松散;
定义不能被子类改变的 algorithm 的骨架,只有某些操作可以在子类中重写;
父类完全控制 algorithm ,仅将具体的步骤与具体的类进行区分;
绑定是在编译时完成的。
模板模式:
它基于框架或抽象类,甚至可以有一个具有默认实现的具体类。
模块耦合得更紧密;
它通过修改方法的行为来改变对象的内容;
它用于在 algorithm 族之间切换;
它在运行时通过其他 algorithm 完全 replace 一个algorithm 来改变对象的行为;
绑定在运行时完成