设计模式->策略模式

从简单的应用开始
首先创建一个Duck父类,其他所有鸭子来继承
所有鸭子都有嘎嘎叫和戏水
例如

Duck中有
quack(); //不是抽象方法
swim(); //不是抽象方法
display(); //抽象方法
//其他鸭子方法

子类
GreenHeadDuck     
display(){        
  //绿头鸭           
}   

子类
RedHeadDuck
 display(){     
 //红头鸭
}

但是现在需要鸭子会飞
只需要在Duck类中添加一个fly()方法,所有鸭子都会继承它
例如

Duck中有
quack(); //不是抽象方法
swim(); //不是抽象方法
display(); //抽象方法
fly(); //所有子类继承
//其他鸭子方法

但是出大问题了,不是所有的Duck的子类都应该会飞,给Duck类添加新的行为时,也给某些Duck子类添加了不适合的行为

对代码的局部更新导致了非局部的副作用 例如(飞行的橡皮鸭)
可以重写橡皮鸭的fly()方法,重写为不做任何事
但是当我们给程序添加木质诱饵鸭时,诱饵鸭不会飞也不会叫,要重写quack()方法和fly()方法为不做任何事
想用继承来达到复用的目的,当涉及到维护时,效果并不那么好

继承可能不是答案,因为需求规约会保持变化,每一次程序中添加新的Duck子类,都需求被迫检查,有可能还要重写fly()和quack()直到永远
因此需要更干净的方式,只让某些(而不是全部)鸭子类型飞或嘎嘎叫
那么用接口怎么样?
把fly()从Duck父类拿出来,并且做一个带fly()方法的Flyable()接口。这样只有能够飞的鸭子实现该接口,并且有一个fly()方法...
同样,做一个Quackable,因为不是所有鸭子都能嘎嘎叫

例如
例如

接口:Flyable      接口Quackable
fly()方法         quack()方法
 
父类Duck
swim();
display();

子类
GreenHeadDuck      RedheadDuck
display()          display()
fly()              fly()
quack()            quack()
 
RubberDuck(橡皮鸭) DecoyDuck(诱饵鸭)
display()          display()
qucuk()

这个设计怎么样?

看似解决了问题,但是当你需要对所有飞行Duck子类的飞行行为做小小改变时,你的感觉怎么样?

我们知道,不是所有子类都有飞行和嘎嘎行为,因此继承不是正确的答案
让子类实现Flyable和/或Quackable解决了部分问题(没有了不恰当的飞行橡皮鸭),但是它完全摧毁了这些行为的代码复用
因此,它只是创造了一个不同的维护噩梦。当然,还有可能出现:飞行鸭子有多种飞行行为

如果有一种方法,当我们需要变更时,我们可以使用对现有代码影响最小的方式,那我们就可以花更少的时间重写代码,让程序去做更酷的事情

思考一下,软件开发中唯一不变的是什么?你唯一深信不疑的是什么?
不管在哪里工作,构造什么软件,用什么语言编程,一直伴随,唯一不变的东西是什么?

是变化,不管应用设计的多好,随着时间推移,应用必定成长和变更

现在聚焦于问题
继承不能很好的解决问题,因为各个子类的鸭子行为一直在改变,让所有子类都拥有这些事不适合的,
Flyable和Quackable接口一开始似乎还听不错(只有会飞的鸭子才实现),除了一点,Java接口通常没有实现的代码,因此没有代码复用。
无论何时,如果你需要修改一个行为,常常被迫往下追踪到所有定义了该行为的子类并修改它,在这个过程中,可能会引入新的bug

针对这种情况有一种设计原则:识别应用中变化的方面,把他们和不变的方面分开
换句话说,如果每次由新的需求,某方面的代码就要变,那么你就知道了,这个行为需要抽取出来,与其他不变的代码分离
另一种思考方式:把会变化的部分取出来并封装,这样以后就可以修改或扩展这个部分,而不会影响其他不需要变化的部分,代码变更引起的不经意后果变小,系统更加有弹性了

开始从Duck类抽出鸭子的行为
目前我们能分辨的是,除了fly()和quack()问题之外,Duck类还算正常,没有其他看起来经常变化的地方
因此,除了一些小的变更,先把Duck类放到一边

现在分离变和不变的部分。创建两组类
一组和fly相关,另一组和quack相关,每一组持有各自行为的所有实现
例如,我们可以有一个类实现嘎嘎叫,另一个实现吱吱叫,另一个实现沉默不语

我们知道fly()和quack()是Duck类中因不同鸭子而变化的部分
为了从Duck类分离这些行为,我们把两个方法都从Duck类抽出,并创建一组新的类来表示每个行为

Duck类依然是所有鸭子的父类,但我们把飞行和嘎嘎叫行为抽出来,并把他们放进另一个类结构
现在,飞行和嘎嘎叫各种获得自己的一组类

设计Duck的行为
那该怎么设计实现飞行和嘎嘎叫行为的类?

我们希望保持各种东西的弹性,毕竟一开始,是鸭子行为的僵化让我们陷入困境。我们也知道,我们要分配行为Duck的实例

例如,我们可能要实例化一个新的绿头鸭实例,并在初始化时指定特点类型的飞行行为
既然这样做了,为什么不确保我们能够动态地改变鸭子的行为?
换句话说,我们应该在Duck类中包含设置行为的方法,这样,我们就能够在运行时改变绿头鸭的飞行行为

让我们来看看第二个设计原则:针对接口编程,而不是实现编程

我们将使用接口来表示每一个行为,行为的每个实现将实现其中一个接口,因此,这一次不是Duck类实现飞行和嘎嘎接口

从现在开始,Duck的行为将放在分离的类中,实现特点行为接口的类,这样Duck类不需要知道行为的任何实现细节
我们专门做了一组类表示行为(例如,吱吱叫),这就是行为类,由行为类而不是Duck类来实现行为接口

这和我们之前所做的大有不同,行为类既来自父类的Duck的具体实现,也通过在子类自身中提供一个特化实现得到
两张情况都依赖于实现。所有代码被锁定使用特定实现,没有改变行为的空间了(除非写更多的代码)

在我们的新设计中,Duck子类将使用接口(FlyBehavior和QuackBehavior)所表示的行为,
这样行为实际的实现(实现FlyBehavior和QuackBehavior的特定具体行为)不会锁定在Duck子类中

例如
接口:FlyBehavior
void fly();

实现类
FlyWithWings                  FlyNoWay
fly(){                           fly(){
//实现鸭子飞行        //什么都不做——不会飞 
}                              }

从现在开始,Duck的行为将放在分离的类中,实现特点行为接口的类
这样,Duck类不需要知道行为的任何实现细节

针对接口编程真正的意思是针对超类型编程
接口一词在这里有多个含义,接口是一个概念,也是Java的一个构造,针对接口编程不是真的使用Java接口,
要点是通过针对超类型编程来利用多态,这样,实际的运行时对象不会被锁定到代码

我们可以重新描述针对超类型编程为变量所声明类型应该是超类型,通常是抽象类或接口,
这样,分配给这些变量的对象可以是超类型的任何具体实现,这意味这类声明时不必知道实际的对象类型

下面是一个使用多态类型的简单例子,详细一个抽象类Animal,它有两个具体实现,Dog和Cat
对实现编程是
Dog d =new Dog();
d.brak();
声明变量d为Dog类型(一个Animal的具体实现)强迫我们针对具体实现编程

而针对接口编程/超类编程则是
Animal animal =new Dog();
animal.makeSound();

我们知道它是一个Dog,但我们可以多态的使用animal引用
更棒的是,子类型再实例化不用在代码中硬编码(像new Dog()),而是在运行时分配具体的实现对象:
a=getAnimal();
a.makeSound();
我们不知道实际的动物子类型,我们在意的只是它知道如何响应makSound()

实现Duck的行为

接口 
FlyBeahavior
//所有飞行的类都要实现的接口,所有新的飞行类只需要实现fly()方法
fly();

实现类
FlyWithWings                    FlyNoway
fly(){                          fly(){                
​//实现鸭子                        //什么都不做->不会飞  	
​//这是所有有翅膀的飞行实现          //这是所有不会飞的鸭子的实现
}                               }

接口
QuackBehavior
//嘎嘎叫行为也一样,它只包含一个需要实现的quack()方法

实现类
Quack                Squeak              MuteQuack
quack(){             quack(){            quack(){
 //实现鸭子嘎嘎叫      //橡皮鸭子嘎嘎叫     //不做任何事->不会叫
 //真的嘎嘎叫         //实则为吱吱叫       //名为嘎嘎叫,实则不出声
}                    }                    }

通过这个设计,其他类型的对象可以服用飞行和嘎嘎叫行为,因为这些行为不再隐藏在Duck类中
我们可以增加新的行为,不用修改任何已有行为类或者涉及任何使用飞行行为的Duck类

整合Duck的行为
关键在于:Duck类现在将委托其飞行和嘎嘎叫行为,而不是使用Duck类(或子类)中定义的嘎嘎叫和飞行方法

做法如下:
1.首先,添加两个实例变量,类型为FlyBeahavior和QuackBehavior,名称为flyBeahavior和quackBehavior。
在运行时,每个具体鸭子对象将给这些变量分配特定行为,像飞行的FlyWithWings...
移出Duck类和任何子类中的fly()和quack()方法
将用两个类似的方法proformFly()和proformQuack()方法来替代

Duck
FlyBeahavior flyBeahavior
QuackBehavior quackBehavior

proformFly();
performQuack();
swim();
display();
//其他鸭子方法

2.现在我们完成这个performQuack()方法
public void performQuack(){
  quackBehavior.quack();
}  

Duck只要让quackBehavior所引用的对象为它嘎嘎叫即可,在这部分代码中我们不关心Duck是哪种对象,只要它知道怎么quack()就行了

3.如何设置flyBehavior和quackBehavior实例变量

public class RedHeadDuck extends Duck{
  public RedHeadDuck(){
    //继承来自Duck的quackBehavior和flyBehavior实例变量
    //使用Quack类来处理嘎嘎叫,因此performQuack()被调用时,嘎嘎叫的责任被委托给Quack对象
    quackBehavior=new Quack();
    //使用FlyWithWings作为其flyBehavior类型
    flyBehavior=new FlyWithWings();
  }
}

4.测试

public class Demo{
  public static void main(String[] args){
    Duck redHeadDuck = new RedHeadDuck();
    redHeadDuck.performQuack();
    redHeadDuck.proformFly();
  }
}

也可以动态的设置行为
加上两个set方法到Duck类

public void setFlyBehavior(FlyBehavior fb){
    this.flyBehavior=fb;
}

public void setQuackBehavior(QuackBehavior qb){
    this.quackBehavior=qb;
}

要在运行时改变鸭子的行为,只需要调用鸭子的set方法

posted @ 2024-01-25 23:28  狗狗没有坏心眼  阅读(9)  评论(0编辑  收藏  举报