设计模式—— 策略模式
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类的成员变量”
组合的定义见:
封装起来的各种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方法动态切换角色的武器
答案:
以上是关于设计模式—— 策略模式的主要内容,如果未能解决你的问题,请参考以下文章