一、策略模式(Strategy Pattern)
本章目录如下:
一、阶段一
二、阶段二
三、策略模式说明
四、本章用到的OO设计原则
五、模式问答
六、本章要点
本章需求是设计一个鸭子程序,并通过三个原则来优化鸭子应用,进而得出前人的设计经验之一策略模式。下面以代码的迭代演化过程为线索介绍。
一、阶段一
需求:制造各种各样的鸭子。
方案1:公共行为放到超类中,不同的行为通过继承在子类中实现。
方案1缺点:
1)、代码在多个子类中重复:因为不同的行为是一个有限集合,所以即便是子类中覆盖实现的函数与其他子类的该函数也有很多重复代码。
2)、运行时行为不容易改变。
3)、很难知道所有鸭子的全部行为:因为子类太多。
4)、上层改变(包括插入、删除、修改)时会牵一发动全身,造成其他子类不想要的改变。使用接口可以解决该问题,但是缺点也很明显:a)、无论何时你需要修改某个行为,你必须得往下追踪并在每一个定义此行为的类中修改它,一不小心,可能会造成新的错误! b)、代码无法复用。
现在我们知道使用继承并不能很好地解决问题,因为鸭子的行为在子类里不断地改变,并且让所有的子类都有这些行为是不恰当的。
二、阶段二
需求:解决方案一的缺点。
设计原则1:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化 T个T的代码混在一起。该设计原则作用: “把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分”。
应用这一原则就解决了方案一的缺点!
鸭子的行为是鸭子模型中的变化之处,根据设计原则1,我们把鸭子的行为独立出来。怎么设计鸭子的行为呢?此时需要运用一个新的原则,即设计原则2。
设计原则2:针对接口编程,而不是针对实现编程。===我的理解是这个原则的使用优先级排在“设计原则1:变化原则”之后,即该原则是一个在大模式已确定需要实现具体类时针对实现类采用的原则,针对范围比较小。
原则2解释:“针对接口编程”真正的意思是“针对超类型 (superype)编程”,关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。“针对超类型编程”这句话,可以更明确地说成“变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型!
如何将行为类和鸭子类结合起来呢?用组合即可(此处指将多个类结合起来使用,并不是说组合模式)。由此引出了第三个设计原则。设计原则3:多用组合,少用继承。
组合原则的优点:使用组合建立系统具有很大的弹性,不仅可以将算法簇封装成类,更可以“在运行时动态的改变行为”,只要组合对象符合正确的接口标准即可。
public abstract class Duck {//鸭子基类
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck() { }
public void setFlyBehavior(FlyBehavior fb) {
flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb) {
quackBehavior = qb;
}
abstract void display();
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
}
===================================================
public class MallardDuck extends Duck {//绿头鸭
public MallardDuck() {
quackBehavior = new Quack();//虽然说过不针对具体实现编程,但是这是本书第一个例子,后续介绍更多时我们再实现动态配置,比如使用工厂模式
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.println("I'm a real Mallard duck");
}
}
===================================================
public interface FlyBehavior {//所有飞行行为类必须实现的接口
public void fly();
}
public class FlyWithWings implements FlyBehavior {//该子类给真正会飞的鸭子使用
public void fly() {
System.out.println("I'm flying!!");
}
}
public class FlyNoWay implements FlyBehavior {//该子类给不会飞的鸭子使用
public void fly() {
System.out.println("I can't fly");
}
}
===================================================
public interface QuackBehavior {//所有鸣叫行为类必须实现的接口
public void quack();
}
public class Quack implements QuackBehavior {
public void quack() {
System.out.println("Quack");
}
}
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println("<< Silence >>");
}
}
public class Squeak implements QuackBehavior {
public void quack() {
System.out.println("Squeak");
}
}
===================================================
public class MiniDuckSimulator {
public static void main(String[] args) {
MallardDuck mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
}
}
策略模式整体结构如下:
三、策略模式说明
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
四、本章用到的OO设计原则
1、封装变化
2、多用组合,少用继承
3、针对接口编程,不针对实现编程
注意:设计原则是根本,设计模式是在设计原则之上的总结!
五、模式问答
1、问: 我是不是一定要先把系统做出来,再看看有哪些地方需要变化,然后才回头去把这些地方分离&封装?
不尽然。通常在你设计系统时,预先考虑到有哪些地方未来可能需要变化,于是提前在代码中加入这些弹性。你会发现,原则与模式可以应用在软件开发生命周期的任何阶段。
2、问 库和框架不也是设计模式吗?
答: 些特定的实现,让我们的代码可以轻 库和框架提供了我们某
易地引用,但是这并不算是设计模式。有些时候,库和框架本身会用到设计模式,这样很好,因为一旦你了解了设计模式,会更容易了解这些API是围绕着设计模式构造的。
3、问:开发时找不到合适的模式,怎么办?
在你无法找到适当的模式解决问题时,采用面向对象原则去解决问题。这些原则适用于所有的开发过程,本质上所有的设计模式也是在设计原则的基础上总结出来的。
六、本章要点
1、知道00基础,并不足以让你设计出良好的00系统。良好的00设计必须具备可复用、可扩充、可维护三个特性。
2、大多数的模式和原则,都着眼于软件变化的主题。大多数的模式都允许系统局部改变独立于其他部分。