从简单的应用开始
首先创建一个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方法
标签:fly,策略,子类,接口,模式,鸭子,Duck,行为 From: https://www.cnblogs.com/zhao-zong-yu-hai/p/17988335