策略模式(Strategy Pattern)就这么简单

0x00 举个生活中的例子

某个大人网站是会员制的,金牌会员购买精神食粮打7折,银牌会员打8折,铜牌会员打9折,铁牌会员不打折。也就是说不同的用户在购买精神食粮的时候结算的价格是不一样的,即使你们买相同公司出品的相同食粮,你们的总价格是不一样的,因为根据会员等级不同,有不同的折扣。

也就是说,面对不同会员等级的用户,大人网站会有不同的计价算法。

问过度娘的人都知道,假如你经常问度娘哪里有可堪一撸的资源,此后你浏览不同网站时,给你推荐的广告都是衣服比较少的妹子图,也许你还会想这网站蛮懂我的嘛。如果你经常问度娘哪位老中医比较厉害,以后你上网的时候,网站给你推荐的广告可能就是如何治疗早泄之类的(不要问我为什么知道)。

也就是说,面对不同的用户,度娘的广告系统或谷歌的广告系统会使用不同的广告推荐算法。

这也就是所谓的策略,同样是购买精神食粮,你的就比我的贵,因为我可是金牌会员!映射到程序员的世界里,策略就是算法了,策略模式就是处理同一件事,我可以有好几个不同的策略(算法)。

0x01 没事玩个鸡应用

污污公司开发了个应用叫没事玩个鸡,这是一款娱乐类应用,可以一键玩鸡,有不同品类的鸡给你玩,如三黄鸡、乌鸡、白鸡等。这个应用的内部设计是标准面向对象技术,设计了一个鸡超类(Superclass),让各种鸡继承此超类。我们来看一下代码:

/** * 超类鸡 */
abstract class Chicken {
    /** * 打招呼 */
    public void sayHi() {
        System.out.println("咯咯咯~");
    }

    /** * 鸡的样子,每种品类的鸡样子都不一样,所以该方法是抽象的 * 由具体的鸡来实现自己在屏幕上显示的样子 */
    public abstract void show();
}

/** * 白鸡,继承Chicken类 */
class WhiteChicken extends Chicken {

    @Override
    public void show() {
        // 样子是白色的
    }
}

/** * 乌鸡,继承Chicken类 */
class BlackChicken extends Chicken {

    @Override
    public void show() {
        // 样子是乌黑的
    }
}

/** * 尖叫鸡,继承Chicken类 */
class ScreamChicken extends Chicken {

    /** * 尖叫鸡不会咯咯叫,所以重写sayHi()方法 */
    @Override
    public void sayHi() {
        System.out.println("惨叫声~");
    }

    @Override
    public void show() {
        // 样子是无毛的
    }
}

代码很简单,具体品类的鸡继承超类(父类)鸡,所有的鸡都会叫,所以由父类处理。不同品类的鸡样子不同,所以由具体品类的鸡来实现。至于尖叫鸡是长这样的:

策略模式(Strategy Pattern)就这么简单_第1张图片

没事玩个鸡应用很快火了,出现了很多竞争对手了,这时产品经理想要改变一些玩法来抛开竞争对手,想出了让鸡可以跑动,这样用户可就以玩跑动中的鸡了,程序员一想,这简单嘛,只需要在父类中加一个run()方法就可以了,这样所有品类的鸡都会跑了:

/** * 跑步 */
public void run() {
    System.out.println("拼命跑");
}

改完后就交给测试人员去测试去了。测试人员一测试,天了撸~出了个明显的bug啊,尖叫鸡也会跑!于是将bug提交到了系统中。

程序员一看,这确实不应该让尖叫鸡也能跑,先修复再说,于是他这样改了尖叫鸡类:

/** * 尖叫鸡,继承Chicken类 */
class ScreamChicken extends Chicken {

    /** * 尖叫鸡不会咯咯叫,所以重写sayHi()方法 */
    @Override
    public void sayHi() {
        System.out.println("惨叫声~");
    }

    @Override
    public void run() {
        // 覆盖,什么也不做
    }

    @Override
    public void show() {
        // 样子是无毛的
    }
}

程序员重写了run()方法,然后什么也不实现,这样修复了bug。通过这个bug程序员也体会到了一件事:当涉及“维护”时,为了”复用(reuse)”目的而使用继承,并不太完美。

没事玩个鸡应用更新后更火了,于是产品经理决定每个月更新一次产品(至于更新的方法,他们还没有想到)。

程序员接到产品经理的更新计划就想,以后万一又要增加一些品类的鸡会怎样?万一有的功能部分品类的鸡是不具备的呢?那不是又得改父类又得改子类,牵一发而动全身,看来需要一个更好的方式才行。于是他开始翻看编程指南,终于找到一个设计原则。

设计原则

找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那你就可以确定,这部分代码需要被抽出来,和其他稳定的代码有所区分,把变化的部分封装起来,以便以后可以轻易的改动或扩展此部分,而不影响不需要变化的部分。

0x02 分开变化和不会变化的部分

从哪里开始呢?我们知道Chicken类的sayHi()方法和run()方法会随着鸡的不同而改变,比如尖叫鸡sayHi()的方式就和别的鸡不一样,它也不会跑。根据上面说的设计原则,需要将它们独立出来,为了把这两个行为从Chicken类中分开,程序员将它们从Chicken类中取出来,建立一组新类来代表每个行为。示意图如下:

策略模式(Strategy Pattern)就这么简单_第2张图片

现在已经把变化和不会变化的部分分开了,需要考虑如何设计鸡的行为类了。继续翻看编程指南,发现了另一个设计原则。

设计原则

针对接口编程,而不是针对实现编程。

现在程序员利用接口代表每个行为,比如,SayHiBehaviorRunBehavior,而行为的每个实现都将实现其中的一个接口。我们来看一下这两个接口的代码:

  • SayHiBehavior接口:
interface SayHiBehavior {
    void sayHi();
}
  • RunBehavior接口:
interface RunBehavior {
    void run();
}

接口定义好了,我们就可以弄一些具体实现了。

  • 普通打招呼:
/** * 普通打招呼 */
class SayHiNormally implements SayHiBehavior {

    @Override
    public void sayHi() {
        System.out.println("咯咯咯~");
    }
}
  • 尖叫打招呼
/** * 尖叫打招呼 */
class SayHiScream implements SayHiBehavior {

    @Override
    public void sayHi() {
        System.out.println("惨叫声~");
    }
}
  • 正常跑步
/** * 正常跑 */
class RunNormally implements RunBehavior {

    @Override
    public void run() {
        System.out.println("拼命跑");
    }
}
  • 不会跑步
/** * 不会跑 */
class RunNoWay implements RunBehavior {

    @Override
    public void run() {
        // 什么也不做,不会跑
    }
}

一切准备就绪,我们该开始重构了。

0x03 重构没事玩个鸡应用

父类Chicken重构如下:

/** * 超类鸡 */
abstract class Chicken {
// /**
// * 打招呼
// */
// public void sayHi() {
// System.out.println("咯咯咯~");
// }
//
// /**
// * 跑步
// */
// public void run() {
// System.out.println("拼命跑");
// }
    // 打招呼行为
    private SayHiBehavior mSayHiBehavior;
    // 跑步行为
    private RunBehavior mRunBehavior;

    /** * 设置打招呼行为 * @param sayHiBehavior */
    public void setSayHiBehavior(SayHiBehavior sayHiBehavior) {
        mSayHiBehavior = sayHiBehavior;
    }

    /** * 设置跑步行为 * @param runBehavior */
    public void setRunBehavior(RunBehavior runBehavior) {
        mRunBehavior = runBehavior;
    }

    /** * 执行打招呼,这个方法替换之前的sayHi()方法 */
    public void performSayHi() {
        // 委托给打招呼行为类
        mSayHiBehavior.sayHi();
    }

    /** * 执行跑步,这个方法替换之前的run()方法 */
    public void performRun() {
        // 委托给跑步行为类
        mRunBehavior.run();
    }

    /** * 鸡的样子,每种品类的鸡样子都不一样,所以该方法是抽象的 * 由具体的鸡来实现自己在屏幕上显示的样子 */
    public abstract void show();
}

程序员将Chicken类的sayHi()方法和run()方法给注释掉了,增加了打招呼行为SayHiBehavior和跑步行为RunBehavior,通过setter来设置。然后用performSayHi()performRun()方法将具体的打招呼行为和跑步行为委托给SayHiBehaviorRunBehavior去做。

测试一下,测试方法长这样:

public class MyTest {

    public static void main(String[] args) {
        System.out.println("白鸡:");
        // 白鸡
        WhiteChicken whiteChicken = new WhiteChicken();
        // 设置白鸡为普通打招呼行为
        whiteChicken.setSayHiBehavior(new SayHiNormally());
        // 设置白鸡正常跑步行为
        whiteChicken.setRunBehavior(new RunNormally());
        // 显示白鸡
        whiteChicken.show();
        // 白鸡打招呼
        whiteChicken.performSayHi();
        // 白鸡打跑步
        whiteChicken.performRun();

        System.out.println("尖叫鸡:");

        // 尖叫鸡
        ScreamChicken screamChicken = new ScreamChicken();
        // 设置尖叫鸡为惨叫打招呼行为
        screamChicken.setSayHiBehavior(new SayHiScream());
        // 设置尖叫鸡不能跑步行为
        screamChicken.setRunBehavior(new RunNoWay());
        // 显示尖叫鸡
        screamChicken.show();
        // 尖叫鸡打招呼
        screamChicken.performSayHi();
        // 尖叫鸡跑步
        screamChicken.performRun();
    }
}

我们给白鸡设置了普通打招呼策略(算法),给尖叫鸡设置的是惨叫打招呼策略(算法),然后给白鸡设置了正常跑步策略(算法),给尖叫鸡设置的是不会跑步策略(算法)

执行结果:

策略模式(Strategy Pattern)就这么简单_第3张图片

完美的实现了策略模式,在运行时想改变鸡的行为,只需要调用setter方法就行了。

拿出随身携带的镜子照一下,帅呆了~,叼就一个字,我说一万次!

0x04 总结

以上就是策略模式了,定义了算法族,分别封装起来,让他们之间可以互相替换。比如鸡的打招呼行为就有普通打招呼和惨叫打招呼,想用哪个就用哪个。在这里也使用了像多态组合来辅助实现策略模式。多用组合,少用继承也是一个设计原则。

0x05 参考文献

  • Head First 设计模式

你可能感兴趣的:(设计模式,策略模式,strategy)