本文解决问题
- 什么是策略模式?
- 策略模式的优缺点以及策略模式解决了什么痛点
- 策略模式的适用环境
什么是策略模式?
策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。
策略模式是一种对象行为型模式。
说定义恐怕总会令人迷惑,其实这里的算法就是我们日常所说的行为,举个栗子:
这是一个模拟鸭子游戏:游戏中会出现各种鸭子,一边游泳,一边叫。
这个应用用的是OO技术有一个鸭子超类(superclass),并让各种鸭子继承此类。
public abstract class Duck {
public Duck() {}
abstract void display();
public void quack() {}
public void swim() {}
}
public class MallardDuck extends Duck {
//外观是绿头鸭
public MallardDuck() {}
public void display() {}
}
public class RedHeadDuck extends Duck {
public RedHeadDuck() {}
public void display() {}
}
但是产品经理组织了一场头脑风暴,然后决定要让鸭子们会飞,我们怎么做?程序猿们说 很简单于是
public class Duck {
public Duck() {}
public void quack() {}
public void swim() {}
public void fly(){}
}
所有的鸭子都会飞了,但是……,他们的游戏里面有一只橡皮鸭,产品经理摊手表示,橡皮鸭是为什么会飞的。。。。这时候你会怎么办?
OO来说我们继承覆盖就好了
public class RubberDuck extends Duck {
public void display() {};
public void quack(){};
public void fly(){
//什么都不做
}
}
但是如果以后加入铁鸭子怎么办?不会飞也不会叫
那下面我们用OO中的接口如何?也就是说把fly()quack()写成接口,下面的鸭子进行继承。会有什么问题呢?
如果是你你会怎么办?
采用良好的00软件设计原则:
- 原则一:
找出应用中可能需要变化的地方,把它们独立出来,不要和那些不需要变化的代码混在一起,这时候你就可以很好的修改或者扩展这部分,而不影响不需要变化的其他部分。
到底哪一些是Duck类变得以及不变的?
- fly()与quack()会随着不同而改变
- 你没有办法开始就知道,但是可以再改需求的时候慢慢修改提炼
- 原则二:
针对接口编程,而不是针对实现编程
这个对于我们OO来说很需要了解,我目前总是针对实现编程而不是接口编程。在栗子中怎么实现呢?那就是鸭子的行为将被放在分开的类中,专门提供某行为的接口的实现,鸭子的类就不需要知道行为的实现细节
举个栗子:
public interface FlyBehavior {
public void fly();
}
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("I can't fly");
}
}
public interface QuackBehavior {
public void quack();
}
public class Quack implements QuackBehavior {
public void quack() {
//呱呱叫
System.out.println("Quack");
}
}
public class Squeak implements QuackBehavior {
public void quack() {
//橡皮鸭子吱吱叫
System.out.println("Squeak");
}
}
public class MuteQuack implements QuackBehavior {
public void quack() {
//什么都不做,我不会叫
System.out.println("<< Silence >>");
}
}
那这些有什么好处?对了行为单独一个类,实现了解耦,这些行为和鸭子无关了,以后Nathan要什么的鸭子,我们程序猿都可以给他了。
这时候有个问题,我们要在什么时候分离封装呢?
解耦很清晰,可是我们要怎么整合起来呢?让鸭子更像一个鸭子,先要在duck加两个实体变量
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
abstract void display();
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehavior.quack();
}
public void swim() {
System.out.println("All ducks float, even decoys!");
}
}
public class MallardDuck extends Duck {
public MallardDuck() {
//在构造绿头鸭的时候装配上叫和飞的行为,这个绿头鸭就真的会飞和叫了
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
public void display() {
System.out.println("I'm a real Mallard duck");
}
}
编写好了,如何测试呢?
public class MiniDuckSimulator {
public static void main(String[] args) {
MallardDuck mallard = new MallardDuck();
RubberDuck rubberDuckie = new RubberDuck();
DecoyDuck decoy = new DecoyDuck();
mallard.performQuack();
rubberDuckie.performQuack();
decoy.performQuack();
}
}
讲了这么多,我们从新的梳理一下,到底整体的一个架构是什么样子的,这时候请不要把鸭子的行为说成行为,我们把行为想象成一族算法,这样行为是不是就可以放在很多地方了?
- 原则三
多用组合,少用继承。
上面我们看了组合建立系统的具有很大的弹性,可以把算法族封装成类,还可以随意组合,只要符合正确的接口。
以后要做什么捕捉鸭子的鸭鸣器继承一个算法就可以很快实现。
上述其实讲解OO原则的时候你已经学会了策略模式。
策略模式的优点
- 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 策略模式提供了管理相关的算法族的办法。
- 策略模式提供了可以替换继承关系的办法。
- 使用策略模式可以避免使用多重条件转移语句。
策略模式的缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
策略模式的适用范围
做面向对象设计的,对策略模式一定很熟悉,因为它实质上就是面向对象中的继承和多态,在看完策略模式的通用代码后,我想,即使之前从来没有听说过策略模式,在开发过程中也一定使用过它吧?至少在在以下两种情况下,大家可以考虑使用策略模式,
- 几个类的主要逻辑相同,只在部分逻辑的算法和行为上稍有区别的情况。
- 有几种相似的行为,或者说算法,客户端需要动态地决定使用哪一种,那么可以使用策略模式,将这些算法封装起来供客户端调用。
策略模式是一种简单常用的模式,我们在进行开发的时候,会经常有意无意地使用它,一般来说,策略模式不会单独使用,跟模版方法模式、工厂模式等混合使用的情况比较多。
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
- 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。