设计模式—— 策略模式

Posted 玛丽莲茼蒿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式—— 策略模式相关的知识,希望对你有一定的参考价值。

无论是7个设计原则还是设计模式,完全遵循是很难的,但应有意识尽量遵循。


“以往是代码复用,设计模式是经验复用” 

接下来,会在设计模式的讲解中贯穿设计原则

策略模式总结

对于经常发生变化的代码,我们脑子里要有这样的图:

把“变化”拿出来,这就完成了策略模式的第一步。

第二步就是把各种变化写成算法族。

 这就实现了策略模式。

初始化的时候,子类需要什么行为,直接从算法族里拿。

运行时,也可以任意切换算法族。最容易理解的例子便是“游戏时,角色把武器剑切换成刀,然后又切换成箭”

问题描述

       公司做了一套模拟鸭子游戏: SimUDuck。游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。  

版本(一) 

        下面这个版本是所有学过面向对象基础(封装-继承-多态)的人都能设计出来的,也是第一直觉的。

 

        如果我要新增一个鸭子,只需要增加一个类去继承超类Duck,然后重写display()方法就可以了。我们利用了继承带来的优点——对于quack()和swim()这种鸭子共有的行为,实现了代码的复用,这种设计听起来很不错。

        但是如果甲方要求给鸭子增加一个新的行为fly()呢?那么就在超类里增加一个fly方法,对于绿头鸭和红头鸭来说都会飞,这样是行得通的。

        可是如果我们有更多其他的鸭子,他们有的会飞有的不会飞,叫声也不一样。这是我们发现,对于不同的鸭子类,不仅dispaly()需要重写,quack()和fly()也需要每次判断、重写。

        比如,新来了一个橡皮鸭,quack要重写成“吱吱叫”,fly要重写成“不会飞”。

        又来了一个木头傻鸭,我们又要进行判断,判断结果是它不会叫也不会飞,进而重写quack和fly。

         由此可以看出,版本(一)只利用了继承,虽然带来了代码复用的优点。但也带来了继承的弊端

  • quack和fly的代码在多个子类中重复
  • 每当有新的鸭子出现,程序员就要被迫检查新鸭子有没有quack和fly的行为
  • 很难知道鸭子的全部行为。如果又出现类似fly的新行为,又要更改超类,进而“牵一发而动全身”,重新判断各个子类有没有这个行为。
  • 运行时的行为不易发生改变。比如运行的时候鸭子翅膀断了(我好残忍),没有使其变得不会飞的代码

所以,只使用继承是不行的。

版本(二)

根据我们的设计原则——封装变化

        首先理解什么是“变化”:就是经常需要扩充、修改的部分

        如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出单独封装起来。这样我们以后就可以轻易扩充此部分,而不影响其他稳定的代码。

fly()和quack()经常发生扩充或修改,我们把它们取出来。

我们采用接口的方式将fly和quack行为独立出来:

 但我们又会面对接口带来的弊端:

  • 在各个鸭子类中,要实现大量fly()和quack()的重写,和版本(一)相比,好像并没有好到哪里去(继承带来的代码量重复消除了,但是转移到接口上去了)

那如何更好地“封装变化”呢?我们不让各个鸭子类自己去实现fly和quack,而是制造专门实现fly和quack行为的类,这就称为“行为类

 版本(三)

 除了用到前面的“封装变化”原则外,版本(三)还用到了面向接口编程原则多用聚合少用继承原则,使得鸭子的行为在运行时可以动态改变。

面向接口编程原则 / 依赖倒置原则:面向接口编程,而不是面向实现编程。

多用聚合少用继承原则:鸭子的行为不是继承来的,而是和行为类组合来的。这种组合体现在“行为类的引用作为Duck类的成员变量”

组合的定义见:

java学习笔记(七)—— 封装(类与对象)_玛丽莲茼蒿的博客-CSDN博客

封装起来的各种fly行为类和quack行为类又叫“算法族” ,这就是本节要学的策略模式。

策略模式:定义了算法族,封装起来,让他们之间可以互相替换。此模式让算法的变化独立于使用算法的客户

完整代码如下: 

 

 

 Duck.java

abstract public class Duck 
    FlyBehavior flyBehavior;
    QuackBehavior quackBehavior;
    public void swim()
        System.out.println("是个鸭子都会游泳,难道有不会的吗?");
    
    public abstract void display();
    public void setFlyBehavior(FlyBehavior flyBehavior)
        this.flyBehavior = flyBehavior;
    
    public void setQuackBehavior(QuackBehavior quackBehavior)
        this.quackBehavior = quackBehavior;
    
    public void performFly()
        flyBehavior.fly();
    
    public void performQuack()
        quackBehavior.quack();
    

实现一个绿头鸭MallardDuck.java

public class MallardDuck extends Duck
    public MallardDuck() //依赖传递法(一)
        flyBehavior = new FlyWithWings(); //初始化,绿头鸭可以飞
        quackBehavior = new Quack(); //初始化,绿头鸭呱呱叫
    

    @Override
    public void display() 
        System.out.println("我有绿色的头");
    

客户端测试:

public class Client 
    public static void main(String[] args) 
        MallardDuck mallardDuck = new MallardDuck();
        mallardDuck.display();
        mallardDuck.swim();
        mallardDuck.performFly();
        mallardDuck.performQuack();

        //现在,绿头鸭突然失去了翅膀
        System.out.println("-----------\\nUnfortunately, 绿头鸭失去了翅膀\\n-----------");
        mallardDuck.setFlyBehavior(new FlyNoWay());
        mallardDuck.performFly();
    

看,对绿头鸭fly行为的更改是不是十分方便!

如果甲方提出来一个“用火箭带着鸭子飞”的fly行为,我们也很容易加上去。只需要新增一个fly的行为类就可以了

public class FlyWithRocket implements FlyBehavior
    @Override
    public void fly() 
        System.out.println("火箭带着飞");
    

测试一下:

public class Client 
    public static void main(String[] args) 
        MallardDuck mallardDuck = new MallardDuck();
        mallardDuck.display();
        mallardDuck.swim();
        mallardDuck.performFly();
        mallardDuck.performQuack();

        //现在,绿头鸭突然失去了翅膀
        System.out.println("-----------\\nUnfortunately, 绿头鸭失去了翅膀\\n-----------");
        mallardDuck.setFlyBehavior(new FlyNoWay());
        mallardDuck.performFly();

        //但是绿头鸭有了火箭
        mallardDuck.setFlyBehavior(new FlyWithRocket());
        mallardDuck.performFly();
    

新场景

 

        这里的Character就是上述例子中的超类Duck,我们要把经常发生变化的“使用武器行为”拿出来单独封装,并给出各个行为类(算法族)。

  • 这样,如果游戏发生更新,有了新的武器出现,直接加一个行为类就能实现扩充。
  • 如果游戏发生更新,要在中秋节那天增加一个新角色“嫦娥”,只需要新增一个嫦娥类继承超类Character,然后。。。
  • 如果公司收到大量玩家的建议,“挥剑”动作太丑了要修改,那么也需要修改SwordBehavior这个行为类就可以了。
  • 游戏进行时(程序运行时),可以通过setter方法动态切换角色的武器

 答案:

以上是关于设计模式—— 策略模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之策略模式PHP实现

设计模式之策略模式

设计模式@第25章:策略模式

设计模式---策略模式Strategy Pattern

设计模式 策略模式

设计模式 策略模式