设计模式之策略模式

策略模式的定义

  • 策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Polic
    y)。

策略模式是一种对象"行为型"模式。

策略模式的使用场景

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

策略模式的UML类图

策略模式UML类图1-0.png

角色介绍:

  • Context : 用来操作策略上下文环境。
  • Stragety : 策略的抽象。
  • ConcreteStragetyA , ConcreteStragetyB : 具体的策略实现。

策略模式示例:

  • 假设有一个模拟鸭子的游戏,游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。这个游戏的内部设计了一个鸭子超类Duck,并让各种鸭子继承此超类。
    图1-0.png

    让鸭子能飞
    去年,公司的竞争力加剧,公司主管认为该是创新的时候了。主管认为,此模拟程序需要会飞的鸭子,将竞争者抛在后面。
    图1-1.png
图1-2.png
图1-4.png
图1-5.png

图1-6.png

改进继承
Joe认识到继承可能不是一个好的解决办法,因为他刚刚拿到来自主管的备忘录,希望以后每六个月更新产品(至于更新办法,他们还没想到)。Joe知道规格会常常改变,每当有新的鸭子子类出现,他就要被迫检视并可能需要覆盖fly()和quack().....这简直是无穷尽的噩梦。所以,他需要一个更清晰的方法,让某些(而不是全部)鸭子类型可飞或可叫。
图1-7.png

图1-8.png
图1-9.png

第一个设计原则

设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。这个概念很简单,几乎是每个设计模式背后的精神所在,所有的模式都提供了一套方法让系统中的某部分改变不会影响其它部分。

1. 分开变化和不会变化的部分

  • 就我们目前所知,除了fly()和quack()的问题之外,Duck类还算一切正常。现在,为了要分开“变化和不会变化的部分”,我们准备建立两组类(完全远离Duck类),一个是“fly”相关的,一个是“quack”相关的,每一组类将实现各自的动作。比如说,我们可能有一个类实现“呱呱叫”,另一个类实现“叽叽叫”,另一个类实现“安静”。


    分开变化的部分和不变化的部分.png

2. 设计鸭子的行为

  • 如何设计那组实现飞行和呱呱叫的行为的类呢?
    我们希望一切能有弹性,毕竟,正是因为一开始鸭子行为没有弹性,才让我们走上现在这条路。我们应该在鸭子类中包含设定行为的方法,这样可以实现在“运行时”动态地“改变”鸭子的飞行行为。

第二个设计原则

设计原则:针对接口编程,而不是针对实现编程。

针对接口编程,而不是针对实现编程,举例:

// 针对实现编程
// 声明变量“d”为Dog类型,会造成我们必须针对具体实现编码
Dog d = new Dog();
d.bark();

// 针对接口/超类型编程
// 我们知道该对象是狗,但是我们现在利用animal进行多态的调用
Animal animal = new Dog();
animal.makeSound();

// 更棒的是,子类实例化的动作不再需要在代码中硬编码,
// 例如new Dog(),而是“在运行时才指定具体实现的对象”
// 我们不知道实际的子类型是“什么”,我们只关心它知道如何正确地进行makeSound()的动作就够了
a = getAnimal();
a.makeSound();
针对接口编程.png

所以我们可以利用接口代表行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中一个接口。
所以这次鸭子类不会负责实现Flying与Quacking接口,反而是由我们制造一组其他类专门实现FlyBehavior与QuackBehavior,这就成为“行为”类。

设计鸭子的行为.png

这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现。这两种做法都是依赖于“实现”,我们被“实现”绑得死死的,没办法更改行为。
在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实际的“实现”不会被绑死在鸭子的子类中。

3.实现鸭子的行为

实现鸭子的行为.png

这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子无关了。而我们可以新增一些行为,不会影响到既有的行为类,也不会影响有使用到飞行行为的鸭子类。

4. 集成鸭子的行为

  • 鸭子现在会将飞行和呱呱叫的动作,委托(delegate)别人处理,而不是使用定义在自己类(或子类)内的方法。
    1. 首先,在鸭子中加入两个实例变量, 分别为FlyBehavior与QuackBehavior,声明为接口类型(而不是具体类实现类型),每个变量会利用多态的方式在运行时引用正确的行为类型(例如:FlyWithWings、Squeak . . . 等)。
    2. 我们也必须将Duck类与其所有子类中的fly() 与quack( ) 移除,因为这些行为已经被搬移到FlyBehavior与QuackBehavior类中了。我们用performFly()和performQuack()取代Duck类中的fly()与quack()。
      每一个鸭子都有一个FlyBehavior和QuackBehavior,好将飞行和呱呱叫委托给它们代为处理。


      集成鸭子的行为.png

5.动态的设定行为
在Duck类中,加入两个新方法:

 public void setFlyBehavior(FlyBehavior fb){
        flyBehavior=fb;
 }
  public void setQuackBehavior(QuackBehavior qb){
        quackBehavior=qb;
 }

从此以后,我们可以“随时”调用这两个方法改变鸭子的行为。

代码示例:

Duck.java

/**
 * 抽象鸭子超类
 */
public abstract class Duck {
    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;

    public Duck() {

    }

    public abstract void display();

    public void swim() {
        System.out.println("All ducks float, even decoys!");
    }

    public void performFly() {
        flyBehavior.fly();
    }

    public void performQuack() {
        quackBehavior.quack();
    }

    public void setFlyBehavior(FlyBehavior fb) {
        flyBehavior = fb;
    }

    public void setQuackBehavior(QuackBehavior qb) {
        quackBehavior = qb;
    }

}

DecoyDuck.java

/**
 * 诱饵鸭
 */
public class DecoyDuck extends Duck {

    public DecoyDuck() {
        setFlyBehavior(new FlyNoWay());
        setQuackBehavior(new MuteQuack());
    }

    @Override
    public void display() {
        System.out.print("I'm a duck Decoy");
    }

}

ModelDuck.java

/**
 * 模型鸭
 */
public class ModelDuck extends Duck {
    public ModelDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Quack();
    }

    @Override
    public void display() {
        System.out.println("I'm a model duck");
    }
}

MallardDuck.java

/**
 * 绿头鸭
 */
public class MallardDuck extends Duck {

    public MallardDuck() {
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }

    @Override
    public void display() {
        System.out.println("I'm a real Mallard duck");
    }
}

RubberDuck.java

/**
 * 橡皮鸭
 */
public class RubberDuck extends Duck{
    public RubberDuck() {
        flyBehavior = new FlyNoWay();
        quackBehavior = new Squeak();
    }

    @Override
    public void display() {
        System.out.println("I'm a rubber duckie");
    }
}

RedHeadDuck.java

/**
 * 红头鸭
 */
public class RedHeadDuck extends Duck {
    public RedHeadDuck() {
        flyBehavior = new FlyWithWings();
        quackBehavior = new Quack();
    }

    @Override
    public void display() {
        System.out.println("I'm a real Red Headed duck");
    }
}

FlyBehavior.java

/**
 * 飞行行为接口
 */
public interface FlyBehavior {
    void fly();
}

FlyRocketPowered.java

/**
 * 火箭动力飞行
 */
public class FlyRocketPowered implements FlyBehavior {
    @Override
    public void fly() {
        System.out.println("I'm flying with a rocket");
    }
}

FlyNoWay.java

/**
 * 不会飞
 */
public class FlyNoWay implements FlyBehavior{
    @Override
    public void fly() {
        System.out.println("I can't fly");
    }
}

FlyWithWings.java

/**
 * 实现鸭子飞行
 */
public class FlyWithWings implements FlyBehavior {
    @Override
    public void fly() {
        System.out.print("I'm flying!!");
    }
}

QuackBehavior.java

/**
 * 叫的行为接口
 */
public interface QuackBehavior {
    void quack();
}

Quack.java

/**
 * 嘎嘎叫
 */
public class Quack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Quack");
    }
    
}

FakeQuack.java

/**
 * 假嘎嘎叫
 */
public class FakeQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Qwak");
    }
}

Squeak.java

/**
 * 吱吱叫
 */
public class Squeak implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("Squeak");
    }
}

MuteQuack.java

/**
 * 禁音
 */
public class MuteQuack implements QuackBehavior {
    @Override
    public void quack() {
        System.out.println("<< Silence >>");
    }
}

测试程序 MiniDuckSimulator.java

/**
 * 迷你鸭子模拟器测试程序
 */
public class MiniDuckSimulator {
    public static void main(String[] args){
        MallardDuck mallard = new MallardDuck();
        RubberDuck rubberDuckie = new RubberDuck();
        DecoyDuck decoy = new DecoyDuck();

        Duck model = new ModelDuck();

        mallard.performQuack();
        rubberDuckie.performQuack();
        decoy.performQuack();

        model.performQuack();
        model.setFlyBehavior(new FlyRocketPowered());
        model.performFly();
    }
}

测试结果:

Quack
Squeak
<< Silence >>
Quack
I'm flying with a rocket

总结

  • 所有鸭子从Duck继承,飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口。


    总的类图.png

“有一个”(has a)可能比“是一个”(is a)更好
有一个关系相当有趣:每一鸭子都有一个FlyBehavior且有一个QuackBehavior,让鸭子将飞行和呱呱叫委托它们代为处理。

如果将两个类结合起来使用(如同本例),这就是组合(Composition)。这种作法和继承不同的地方在于:鸭子的行为不是继承而来,而是和适当的行为对象组合而来。

第三个设计原则

设计原则:

  • 多用组合,少用继承
  • 使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以在运行时动态地改变行为。

优点

  • 策略模式主要用来分离算法,在相同的行为抽象下有不同的具体实现策略。这个模式很好的演示了开闭原则,也就是定义抽象,注入不同的实现,从而达到很好的可扩展性。
  • 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
  • 结构清晰明了,使用简单直观。
  • 耦合度相对而言较低,扩展方便。
  • 操作封装也更为彻底,数据更为安全。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
  • 随着策略的增加,策略模式会造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。

Android源码中的策略模式实现

  • 时间插值器

参考资料
《Head First 设计模式》
《Android 源码设计模式解析与实战》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

  • 策略模式,是我们接触到的第一个设计模式,也是较容易理解的一个模式。我们可以给它下一个定义:** 定义了算法族,分别...
    六尺帐篷阅读 704评论 0 8
  • 一直想把常见的设计模式系统地学习一遍,结果和大多数人一样,过了几天就没能坚持下去了。我发现学习这件事情急不得,往往...
    Neulana阅读 563评论 5 2
  • 思考: 假设有个需求,模拟鸭子游戏:在游戏中会出现各种各样的鸭子,一边游泳戏水,一边呱呱叫。开始我们的设计吧: 这...
    MarksGui阅读 180评论 0 0
  • 需求: 1:模拟鸭子项目 从项目"模拟鸭子游戏"开始。 鸭子都会叫、会游泳。有的鸭子是红头的、有的鸭子是绿头的。 ...
    凯哥Java阅读 112评论 0 1
  • http://www.weinisongdu.com/admin/shareOpus/14927251?from=...
    蒋光头jL94430阅读 569评论 20 24