简单模拟鸭子应用
每一种鸭子的外观都不同,display()方法是抽象的。
现在需要鸭子可以飞(使用接口)
这个设计如何?是一个好的设计吗?
并非所有的子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式。虽然Flyable与Quackable可以解决一部分问题,但是却造成代码无法复用,这只能算是从一个恶梦跳进另一个恶梦。
设计原则:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
把会变化的部分取出并“封装”起来,好让其他部分不会受到影响。代码变化引起的不经意后果变少,系统变得更有弹性。
所有模式都提供了一套方法,让系统中某部分改变不会影响其他部分。
分开变化和不会变化的部分
Duck类内的fly()和quack()会随着鸭子的不同而改变。
为了要把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。
设计鸭子的行为
如何设计那组实现飞行和呱呱叫的行为的类呢?我们还想能够“指定”行为到鸭子的实例。比方说,我们想要产生一个新的绿头鸭实例,并指定特定“类型”的飞行行为给它。干脆顺便让鸭子的行为可以动态地改变好了。换句话说,我们应该在鸭子类中包含设定行为的方法,这样可以在“运行时”动态地“改变”绿头鸭的飞行行为。
设计原则:
针对接口编程,而不是针对实现编程。
从现在开始,鸭子的行为将被放在分开的类中,此类专门提供某行为接口的实现。这样鸭子类就不再需要知道行为的实现细节。
可以利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。
在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。(换句话说,特定的具体行为编写在实现了FlyBehavior与QuackBehavior的类中)。
实现鸭子的行为
飞行类接口:
呱呱叫接口:
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经和鸭子类无关了。
而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。
整合鸭子的行为
关键在于,鸭子现在会将飞行和呱呱叫的动作“委托”(delegate)别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。
- 行为变量被声明为行为“接口”类型。
- 实例变量在运行时持有特定行为的引用。
- performQuack()和performFly()方法去掉fly()和quack()方法。
现在,我们来实现performQuack()方法:
public class Duck{
QuackBehavior quackBehavior;
public void performQuack(){
quackBehavior.quack();
}
}
想进行呱呱叫的动作,Duck对象只要叫quackBehavior对象去呱呱叫就可以了。我们不在乎quackBehavior接口的对象到底是什么,我们只关心该对象知道如何进行呱呱叫就足够了。
public class MallardDuck extends Duck{
public MallardDuck(){
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
}
不对具体实现编程,但构造器中制造一个具体的Quack实现类,此时初始化实例变量的做法不够弹性。
测试Duck的代码
public abstract class Duck{
//为行为接口类型声明两个引用变量,所有鸭子都继承
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public abstract void display();
//委托给行为类
public void performFly(){
flyBehavior.fly();
}
public void performQuack(){
quackBehavior.quack();
}
}
动态设定行为
假设我们在鸭子子类中通过“设定方法”来设定鸭子的行为,而不是在i鸭子的构造器内实例化。
在Duck类中添加两个新方法:
public void setFlyBehavior(FlyBehavior fb){
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb){
quackBehavior = qb;
}
从此以后,我们可以随时调用这两个方法改变鸭子的行为。
封装行为的大局观
重新设计后的类结构,所期望的一切都有:鸭子继承Duck,飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口。
不再把鸭子的行为说成是“一组行为”,我们开始把行为想象成是“一族算法”。算法代表鸭子能做的事(不同的叫法和飞行法),这样的做法也能很容易地用于用一群类计算不同省地销售税金。
“有一个”可能比“是一个”更好
“有一个”关系相当有趣:每一个鸭子都有一个FlyBehavior和一个QuackBehavior,好将飞行和呱呱叫委托给它们代为处理。
将两个类结合起来使用,这就是组合,这种做法和“继承”不同的地方在于,鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。
设计原则:
多用组合,少用继承。
使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合地行为对象符合正确地接口标准即可。
策略模式
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法地客户。