设计模式之策略模式

来源:互联网 发布:数据魔方架构 编辑:程序博客网 时间:2024/06/06 19:50

       这里我们还是用《Head First》一书中的例子来进行讲解,一步步来引出策略模式,在前几篇文章中我们分别分享了设计模式入门和单例模式以及非常重要的观察者模式,如果需要大家可以去看看,今天我们来由浅入深的一起来看看策略模式,看看策略模式的定义,在实际编码的过程中能解决那些重要的问题,以及主要是在那些场景下去使用,还有最重要的就是我们在实际的编码中如何一步步去实现


一、策略模式的定义


策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们还可以相互替换,策略模式让算法独立于使用它的客户而独立变化


二、策略模式的使用场景

  • 针对同一问题的多种处理方式,仅仅是具体的行为有差别时
  • 需要安全的封装多种同归类型的操作时
  • 出现同一抽象类有多个子类,而又需要使用 if-else 或者 switch-case 来选择具体的子类时

三、策略模式演绎


       Joe 上班的公司做了一套相当成功的模拟鸭子的游戏,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫,此系统的内部设计使用了标准的 OO 技术,设计了一个鸭子超类,并让各种鸭子继承此超类,下面来看一下模型图:


       上面就是模型图,相信大家一看就一目了然,所有的鸭子都会呱呱叫(quack)也会游泳(swim)所以由超类负责处理这部分的实现代码,因为每一种鸭子的外观都不同,所以 display() 方法是抽象的,然后每个鸭子类型负责实现自己的 display() 行为在屏膜上显示其外观


      去年,公司的竞争力加剧,需要创先的时候到了,主管们确定,此模拟程序需要需要让鸭子会飞,按照 OO 的设计思想实现这个也简单,Joe 直接在父类里加上了 fly() 方法,这样所有的鸭子就都会飞了,但是事情并不是这么简单,Joe 忽略了一件事,并不是所有的鸭子都会飞,在超类中加上新的 fly() 行为,会使某些不适合该行为的也具有该行为,Joe 想到可以把不会飞的鸭子的 fly() 方法覆盖掉,可是我们如果以后又加入一种鸭子,既不会飞也不会叫,这又该怎么办,Joe 意识到可能继承不能解决目前的问题,需求会不断改变,每当新的鸭子子类出现,就需要被迫检查覆盖新增,将会是无穷的噩梦,所有我们需要设计一个更加清晰的方案,来满足随时变化的需求,而不是一味地修改覆盖代码,这样代码的复用性也就变得很差,和我们设计模式的初衷慢慢偏离


       我们知道,并非所有的鸭子子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式,这里我们想到使用接口,我们可以把 fly() 从超类只取出来,放进一个 Flyable 接口中,这么一来,只有会飞的鸭子实现此接口,同样的方式,也可以用来设计一个Quackable 接口,因为不是所有鸭子都会叫,到这里好像是解决了一部分问题,但却也引出了新的问题,虽然 Flyable 和 Quackable 接口可以解决一部分问题,但却造成了代码无法复用,我们从一个坑掉进了另一个坑,甚至以后我们的鸭子中会飞的鸭子的飞行动作可能都不一样,会有多种变化.....


       现在我们知道继承有时候并不能很好的解决问题,因为鸭子的行为在子类里不断的改变,并且让所有的子类都有这些行为是不恰当的,Flyable 和 Quackable 接口一开始觉得还挺不错,从一定程度上解决了问题,但 Java 接口不具有实现代码,所以继承接口无法达到代码的复用,这意味着,无论何时你需要修改某个行为,你必须得往下追踪并在每一个定义此行为的类中来修改它,一不小心可能又会造成新的错误,这样我们就看如何用策略模式来解决我们眼下遇到的问题


1.设计原则:

       找出应用程序中可能需要变化之处,把它独立出来,不要和那些不需要变化的代码混在一起,换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就需要确定,这部分代码需要被抽出来,和其他稳定的代码有所区分,这样的概念很简单,几乎是每个设计模式背后的精神所在,所有的模式都提供了一套方法,让系统的中某一部分改变不会影响其他部分


2.分开变和不变

       现在我们要分开变和不变,我们准备建立两种类完全从 Duck 超类中分离出来,一个是fly相关的,一个是 quack 相关的,我们知道 Duck 类内的 fly() 和 quack() 会随着鸭子的不同而改变 


3.设计鸭子的行为

       如何设计那组实现飞行和呱呱叫的行为类呢?我们希望一切能有弹性,毕竟正是一开始鸭子的行为没有弹性,才让我们走上这条路,我们还想能够指定行为到鸭子的实例,比方说,我们想要产生一个绿头鸭实例,并指定特定行为的飞行行为给它,这里就要说第二个设计原则


4.第二个设计原则

       针对接口编程,而不是针对实现编程,我们利用接口代表每一个行为,比方说,FlyBehabior 和 QuackBehavior,而行为的每个实现都将实现其中的一个接口,所以这次鸭子类不会负责实现 Flying 和 Quacking 接口,反而是由我们制造一组其他类专门实现 FlyBehabior 和 QuackBehavior,这就称为“行为”类,由行为类而不是 Duck 类来实现行为接口,这样的做法迥异于以往,以前的额做法是:行为来自 Duck 超类的具体实现,或是实现某个接口,并由子类自行实现,这两种做法都是依赖于实现,我们被绑得死死的,没办法更改行为除非写更多的代码,在我们新的设计中,鸭子的子类将使用接口(FlyBehabior 和 QuackBehavior)所表示的行为,所以实际的实现不会被绑死在鸭子的子类中,换句话说,特定的具体行为编写在实现了 FlyBehabior 和 QuackBehavior 的类中


5.实现鸭子的行为

在此,我们有两个接口,FlyBehavior 和 QuackBehavior,还有他们对应的类,负责实现具体的行为:


       上面就是模型图,左边的是 FlyBehavior 接口,所有飞行类都实现它,所有新的飞行类都必须实现 fly() 方法,这里我们实现了两种动作,所有有翅膀的鸭子飞行动作,实现了所有不会飞鸭子的动作,右边是 QuackBehavior 接口,包含一个需要是现实的 quack() 方法,并且实现了不同的叫法,呱呱叫和吱吱叫,以及什么都不做,这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了,而我们还可以新增一些行为,不会影响到既有的行为类,也不会影响使用到飞行行为的鸭子类


6.整合鸭子的行为

关键在于,鸭子现在会将飞行和呱呱叫的动作委托别人处理,而不是使用定义在 Duck 类(或子类)内的呱呱叫和飞行方法,做法是这样的:

6.1 首先,在 Duck 类中“加入两个实例变量”,分别为 “flyBehavior” 和 “quackBehavior”,声明为接口类型(而不是具体类实现类型),每个鸭子对象都会动态地设置这些变量以在运行时引用正确的行为类型(例如:FlyWithWings、Squeak 等),我们必须将Duck 类与其所有子类中的 fly() 和 quack() 删除,因为这些行为已经被搬到 FlyBehavior 与 QuackBehavior 类中了,我们用两个相似的方法 performFly() 和 performQuack() 取代 Duck 类中的 fly() 和 quack(),稍后你就会知道为什么



6.2 现在我们用代码来实现


首先是 FlyBehavior 行为接口

/** * 所有飞行行为类必须实现的接口 *  * @author qiudengjiao */public interface FlyBehavior {void fly();}

接下来是 FlyBehavior 行为接口的两个具体实现类 FlyWithWings 和 FlyNoWay

/** * 这是飞行行为的实现,给真会飞的鸭子用 *  * @author qiudengjiao * */public class FlyWithWings implements FlyBehavior {@Overridepublic void fly() {System.out.println("我飞了");}}

/** * 飞行行为的实现,给不会飞的鸭子用 *  * @author qiudengjiao * */public class FlyNoWay implements FlyBehavior {@Overridepublic void fly() {System.out.println("我不会飞");}}

接下来我们来编写叫的行为 QuackBehavior 接口

/** * 所有叫行为类必须实现的接口 *  * @author qiudengjiao */public interface QuackBehavior {void quack();}

接下来是 QuackBehavior 接口的具体实现类 Quack、MuteQuack 和 Squeak

/** * 叫行为的实现,给呱呱叫 *  * @author qiudengjiao * */public class Quack implements QuackBehavior {@Overridepublic void quack() {System.out.println("呱呱叫");}}

/** * 叫行为实现类,什么也不做行为 *  * @author qiudengjiao * */public class MuteQuack implements QuackBehavior {@Overridepublic void quack() {System.out.println("什么也不做");}}

/** * 叫行为实现,吱吱叫 *  * @author qiudengjiao * */public class Squeak implements QuackBehavior {@Overridepublic void quack() {System.out.println("吱吱叫");}}


现在我们来实现我们需求的鸭子超类 Duck 类


/** * Duck 超类 *  * @author qiudengjiao */public abstract class Duck {// 为行为接口类型声明两个接口变量,所有鸭子子类都继承它们FlyBehavior flyBehavior;QuackBehavior quackBehavior;/** * 具体实现委托给飞行行为类 */public void performFly() {flyBehavior.fly();}/** * 具体实现委托给叫行为类 */public void performQuack() {quackBehavior.quack();}// 不会发生更改的行为public void swim() {System.out.println("所有鸭子都会游泳");}/** * 具体外观实现类 */public abstract void display();}

接下来终于到具体的鸭子实现类了,我们来看绿头鸭 MallardDuck 类


/** * 绿头鸭子类(也就是具体的实现类) *  * @author qiudengjiao */public class MallardDuck extends Duck {public MallardDuck() {// 绿头鸭使用Quack类处理呱呱叫,所以当performQuack()被调用时,叫的职责被委托给Quack对象,而我们得到了真正的呱呱叫quackBehavior = new Quack();// 使用FlyWithWings作为FlyBehavior类型flyBehavior = new FlyWithWings();display();}@Overridepublic void display() {System.out.println("我是绿头鸭子");}}

这样就实现了我们具体的鸭子--绿头鸭,别忘了,因为 MallardDuck 继承了 Duck 类,所以具有 quackBehavior 和 flyBehavior 实例变量


接下来我们来测试一下我们能不能实现我们的需求


/** *  Test 类 * @author qiudengjiaoÏ */public class Test {public static void main(String[] arg) {Duck mallard = new MallardDuck();//这会调用MallardDuck()继承来的performQuack()方法,进而委托给该对象的QuackBehavior对象处理//也就是说调用继承来的quackBehavior引用对象的quack()方法mallard.performQuack();//道理同上mallard.performFly();}}


运行结果:



       这样就实现了我们的需求,并且极易以后的扩展维护,由于上面的注释已经非常清楚就不在一一具体说明,相信大家一看就会非常明白,最后希望大家亲自动手去敲一下,绝对会有不一样的效果,今天就写到这,如有错误请指出



原创粉丝点击