《Head First设计模式》 读书笔记01 策略模式
问题的提出
一个模拟鸭子的游戏,游戏中出现各种鸭子,原本的设计是:设计了一个鸭子超类,并让各种鸭子继承此超类。
但是要加新功能:比如会飞的鸭子?
并且产品会不断更新。
两种不好的解决方案:
1.继承基类
如果在基类中加上fly()方法,所有的鸭子都会继承,造成其他鸭子不想要的改变,比如玩具鸭子不想要会飞。
原先设计中,鸭子叫的方法也有问题,各种鸭子叫声不一致,橡皮鸭不会叫。
并且每当有新的鸭子子类出现,都需要检查并可能需要覆盖这些行为。
2.继承接口
如果使用接口来定义行为,子类根据需要实现接口,(比如,只有会飞的鸭子才实现Flyable接口),虽然可以做到行为的定制,但是却造成代码的无法复用,因为接口不提供实现代码。这就意味着,如果需要修改一个行为,那么必须在每一个定义此行为的类中修改它。
设计原则01
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
设计原则02
针对接口编程,而不是针对实现编程。
“针对接口编程”真正的意思是“针对超类型(supertype)编程”,这里所谓的“接口”,更明确地说,变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,针对接口编程的关键就在多态。
利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。
只要是具体实现此超类型的类所产生的对象,都可以指定给这个超类型的变量。这也意味着,声明类时不用理会以后执行时的真正对象类型。
设计原则03
多用组合,少用继承。
“有一个”可能比“是一个”更好。
使用组合建立系统具有很大的弹性,不仅可以将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。
关于鸭子问题的解决方案
分类变化的部分:将飞行和叫的动作从基类Duck中分离出来。
利用接口代替每个行为:定义FlyBehavior与QuackBehavior行为接口,鸭子类并不实现这些接口,是由我们创造一组其他类专门实现FlyBehavior与QuackBehavior。
这样的设计,可以让各种飞行的动作被其他的对象复用,并且新增行为也不会影响到既有行为。
具体做法:在基类Duck中加入两个实例变量,分别为FlyBehavior flyBehavior与QuackBehavior quackBehavior,注意声明为接口类型,而不是具体的实现类型,每个鸭子对象都会动态地设置这些变量以在运行时引用正确的行为类型。
然后在基类中实现方法,调用相应的变量的方法,比如:
public void performFly()
{
flyBehavior.fly()
}
设定变量可以在子类的构造函数中进行,这样就可以为每个子类设定不同的行为,即变量指向不同的类(该类实现了相应的接口)的对象。
策略模式
定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。