最近打算巩固下设计模式,顺便记录下学习笔记,供日后查阅以及分享给需要的道友。参考书籍为《Head First设计模式》中文版,读书笔记的内容绝大部分来自书籍,同时会添加一些自己的理解。如涉及侵权行为,会立即停止更新,并删除所有所有文章。至于为什么要学设计模式,我也说不清。当你觉得自己的代码怎么写的那么蠢、那么臃肿,怎么改一个小需求到处都要改,这个时候就需要考虑下设计模式了。这本书的代码示例语言使用的是Java,这没啥好说的了,下面就直接开始学习吧。
场景描述
书中使用了一个构造不同类型的鸭子的场景,一步步由浅入深地来讲解该模式,建议去读一下原著,带入感比较强。这里我们就只简述下该场景,我们要构造不同类型的鸭子,每种鸭子都有一些共有的行为;另外其中某一种鸭子可能会有特殊的行为。比如说:呱呱叫(quack),每种鸭子都会叫,但是叫声可能会不一样;飞(fly),像野鸭子会飞,普通的鸭子就不会。这种时候我们该怎样去设计一个可以扩展的鸭子模型呢。最容易想到的是,继承。写一个鸭子父类,定义两个抽象方法,子类去重写。(或者实现接口,这两种方式在策略模式面前没有本质区别)这样能暂时解决问题,但是可能会带来如下缺点:
- 代码在多个子类中重复。比如采用继承的方式,很多种类的鸭子都是呱呱叫,那实现这个行为将出现在很多子类中,
- 运行时的行为不容易改变。可以想象出,一旦鸭子的叫声定成呱呱叫,很难在运行时优雅地改成其他叫声。
- 很难知道所有鸭子的全部行为。如果没有把行为定义全,如果鸭子子类有很多之后,修改将是个恶梦。
- 改变会牵一发而动全身,造成其他鸭子不想要的改变。这一点跟上一点有些类似,比如说新增一个行为之后,可能所有的子类都要改一遍。
所以,继承和实现接口并不是适当的解决方案,下面直接看代码,策略模式是如何解决的。
代码示例
将quack、fly行为从duck中抽出来,单独定义成接口,并且每一种行为都有实现类。
123public interface QuackBehavior {public void quack();}123456public class Quack implements QuackBehavior {public void quack() {System.out.println("Quack...");}}123public interface FlyBehavior {public void fly();}123456public class FlyWithWings implements FlyBehavior {public void fly() {System.out.println("I'm flying!!!");}}定义鸭子抽象父类,在父类中定义行为接口变量,并且给变量添加setter方法。在这个类中display()方法是所有鸭子共有的,由各个子类去实现。行为接口变量由构造子类的时候传递,并且提供了修改的方法。
12345678910111213141516171819202122public abstract class Duck {FlyBehavior flyBehavior;QuackBehavior quackBehavior;public abstract void display();public void performFly() {flyBehavior.fly();}public void performQuack() {quackBehavior.quack();}public void setFlyBehavior(FlyBehavior flyBehavior) {this.flyBehavior = flyBehavior;}public void setQuackBehavior(QuackBehavior quackBehavior) {this.quackBehavior = quackBehavior;}}创建一个会呱呱叫的,并且会用翅膀飞的野鸭
123456789101112public class MallardDuck extends Duck {public MallardDuck() {quackBehavior = new Quack();flyBehavior = new FlyWithWings();}public void display() {System.out.println("I'm a real Mallard duck.");}}测试
123456789public static void main(String[] args) {Duck mallardDuck = new MallardDuck();mallardDuck.performQuack();mallardDuck.performFly();}# 输出Quack...I'm flying!!!运行时需要更改fly的方式,只要调用setFlyBehavior,传递一个新的飞行方式即可。
设计原则
针对上面该模式的实现,总结除了如下设计原则:
- 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。在此模式中,就是区分了display与quack、fly,前者是不需要变化的,后者两者是可能需要变化的。把不变的留在duck抽象父类中,把变得单独抽成接口。
- 针对接口编程,而不是针对实现类编程。在此模式中,就是duck父类的行为变量采用定义成接口而非具体子类型,这样就灵活了。
- 多用组合,少用继承。在此模式中,就是不采用duck子类实现行为接口的方式,而是将行为接口以成员变量的方式定义在duck抽象父类中,这样的好处是,当新增一种行为时,不会影响到现有的所有子类,可按需修改。
总结
该模式本身不难理解,难的是在实际场景中如何区分哪些是变得,哪些是不变的,并且能够明白当下策略模式是否适合,如果硬套,可能会使情况变得更复杂。在书中模式讲解结束后, 会有一些习题可以练习,这一点挺好的。所以还是推荐买一本看看。