先假设我们要做一个模拟鸭子的游戏,这群鸭子会在水里游泳、会嘎嘎的叫,那么运用OO的思想,先设计一个鸭子作为父类,然后所有的鸭子都继承这个父类。
现在,鸭子模拟游戏1.0版本就诞生了,这些鸭子继承了父类的游泳、嘎嘎叫,以及各自都有自己的描述。
接下来就是开发鸭子模拟游戏2.0了,现在需要给鸭子扩展一些模拟,例如飞行、还要给鸭子扩展一些种类,例如模型鸭、橡皮鸭。
这时候就可以继续扩展鸭子父类的属性,并且多添加几个子类。
由于模型鸭、橡皮鸭不会叫也不会飞,所以在子类中重载了quack()方法和fly()方法。
但是这个设计也是不合理的,因为每次有新的鸭子出现,就需要对quack()和fly()进行检查,判断这个类型的鸭子需不需要覆盖这两个方法,需要有一种更清晰的方法。
于是鸭子模拟游戏2.1版本就这样诞生了,这个时候将fly()和quack()从父类中提取出来,放入到两个接口中去,只有会飞、会嘎嘎叫的鸭子才会去继承这两个接口。
但是这样设计出来的结构,也是有缺陷的,例如Mallard和RedheadDuck它们的叫声都是“嘎嘎”,但是却写了重复的quack()代码,没有实现代码的复用。
我们需要重新考虑下这个鸭子模拟游戏整体的设计了,首先鸭子的fly()和quack()行为会频繁的根据鸭子的种类进行变化,基于设计原则——找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码放在一起。我们可以把鸭子的fly()和quack()行为独立出来,从Duck类中提出,建立一组新类代表这些行为。
鸭子fly()的方式会有多种,如有的鸭子是普通的飞行、有的鸭子是鸭子中的战斗鸭,它们的飞行就是特技飞行了。但是无论它们以何种方式进行飞行,它们都是属于飞行这一个行为。再考虑到鸭子会进行锻炼,从一只普通的鸭子变成了战斗鸭,那它的飞行模式也进行了升级变成了特技飞行,这时就需要能够动态的改变鸭子的飞行行为,基于设计原则——针对接口编程,而不是针对实现编程。我们可以利用接口代表一个行为,如FlyBehavior和QuackBehavior,由fly行为类和quack行为类去具体实现。
剩下的工作就是把这些行为整合到Duck类中了,可以在Duck定义两个变量,类型为FlyBehavior和QuackBehavior,每个鸭子对象都可以动态的设置这些动作运行时具体的行为类型。
于是鸭子模拟游戏3.0诞生了
/** * 鸭子飞行为 * * @author ousy * @since 2019-01-27 23:21:23 */ public interface FlyBehavior { /** * 鸭子飞行为 */ public void fly(); }
/** * 鸭子叫行为 * * @author ousy * @since 2019-01-27 23:21:38 */ public interface QuackBehavior { /** * 鸭子叫行为 */ public void quack(); }
/** * 鸭子普通飞行为 * * @author ousy * @since 2019-01-27 23:28:33 */ public class NormalFly implements FlyBehavior { /** * 鸭子飞行为 */ @Override public void fly() { System.out.println("普通的飞"); } }
/** * 鸭子特技飞行为 * * @author ousy * @since 2019-01-27 23:28:41 */ public class SpecialFly implements FlyBehavior { /** * 鸭子飞行为 */ @Override public void fly() { System.out.println("耍特技的飞"); } }
/** * 鸭子普通的叫 * * @author ousy * @since 2019-01-27 23:28:47 */ public class NormalQuack implements QuackBehavior { /** * 鸭子叫行为 */ @Override public void quack() { System.out.println("鸭子普通的叫"); } }
/** * 鸭子特技叫行为 * * @author ousy * @since 2019-01-27 23:29:01 */ public class SpecialQuack implements QuackBehavior { /** * 鸭子叫行为 */ @Override public void quack() { System.out.println("鸭子特技的叫"); } }
/** * 鸭子类 * * @author ousy * @since 2019-01-27 23:21:57 */ public class Duck { /** * 定义fly行为 */ private FlyBehavior flyBehavior; /** * 定义quack行为 */ private QuackBehavior quackBehavior; /** * 游泳 */ public void swim() { System.out.println("鸭子在水里游泳"); } /** * 描述 */ public void display() { } /** * 鸭子飞 */ public void fly() { flyBehavior.fly(); } /** * 鸭子叫 */ public void quack() { quackBehavior.quack(); } /** * 改变飞行行为 * * @param flyBehavior 飞行行为 */ public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } /** * 改版叫行为 * * @param quackBehavior 叫行为 */ public void setQuackBehavior(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; } }
/** * 普通的鸭子 * * @author ousy * @since 2019-01-27 23:32:01 */ public class NormalDuck extends Duck { /** * 鸭子描述 */ @Override public void display() { System.out.println("一只普通的鸭子"); } }
public static void main(String[] args) { Duck duck = new NormalDuck(); duck.display(); duck.setFlyBehavior(new NormalFly()); duck.setQuackBehavior(new NormalQuack()); duck.fly(); duck.quack(); System.out.println("普通的鸭子经过了锻炼变成了一只战斗鸭"); duck.setFlyBehavior(new SpecialFly()); duck.setQuackBehavior(new SpecialQuack()); duck.fly(); duck.quack(); }
输出
一只普通的鸭子
普通的飞
鸭子普通的叫
普通的鸭子经过了锻炼变成了一只战斗鸭
耍特技的飞
鸭子特技的叫
这就是策略模式,它定义了算法族,分别封装了起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
这种模式可以用于设计使用多种数据库的组件,例如程序需要根据情况去访问不同的数据库,MySQL、Oracle、SQLServer,可以将各个数据库的连接对象继承一个接口,然后根据具体情况去使用不同的连接。
策略模式可以很方便的添加新的策略,如鸭子除了NormalQuack、SpecialQuack,它还会WhisperyQuack(轻声的叫),这样就只要创建一个WhisperyQuack类继承QuackBehavior就可以实现了。
但是它也有缺点,策略的增多不可避免的会导致类越来越多,而且策略之间的算法是平级的,他们不能实现相互的调用。