某个大人网站是会员制的,金牌会员购买精神食粮打7折,银牌会员打8折,铜牌会员打9折,铁牌会员不打折。也就是说不同的用户在购买精神食粮的时候结算的价格是不一样的,即使你们买相同公司出品的相同食粮,你们的总价格是不一样的,因为根据会员等级不同,有不同的折扣。
也就是说,面对不同会员等级的用户,大人网站会有不同的计价算法。
问过度娘的人都知道,假如你经常问度娘哪里有可堪一撸的资源,此后你浏览不同网站时,给你推荐的广告都是衣服比较少的妹子图,也许你还会想这网站蛮懂我的嘛。如果你经常问度娘哪位老中医比较厉害,以后你上网的时候,网站给你推荐的广告可能就是如何治疗早泄之类的(不要问我为什么知道)。
也就是说,面对不同的用户,度娘的广告系统或谷歌的广告系统会使用不同的广告推荐算法。
这也就是所谓的策略,同样是购买精神食粮,你的就比我的贵,因为我可是金牌会员!映射到程序员的世界里,策略就是算法了,策略模式就是处理同一件事,我可以有好几个不同的策略(算法)。
污污公司开发了个应用叫没事玩个鸡,这是一款娱乐类应用,可以一键玩鸡,有不同品类的鸡给你玩,如三黄鸡、乌鸡、白鸡等。这个应用的内部设计是标准面向对象技术,设计了一个鸡超类(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() {
// 样子是无毛的
}
}
代码很简单,具体品类的鸡继承超类(父类)鸡,所有的鸡都会叫,所以由父类处理。不同品类的鸡样子不同,所以由具体品类的鸡来实现。至于尖叫鸡
是长这样的:
没事玩个鸡应用很快火了,出现了很多竞争对手了,这时产品经理想要改变一些玩法来抛开竞争对手,想出了让鸡可以跑动,这样用户可就以玩跑动中的鸡了,程序员一想,这简单嘛,只需要在父类中加一个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)”目的而使用继承,并不太完美。
没事玩个鸡应用更新后更火了,于是产品经理决定每个月更新一次产品(至于更新的方法,他们还没有想到)。
程序员接到产品经理的更新计划就想,以后万一又要增加一些品类的鸡会怎样?万一有的功能部分品类的鸡是不具备的呢?那不是又得改父类又得改子类,牵一发而动全身,看来需要一个更好的方式才行。于是他开始翻看编程指南,终于找到一个设计原则。
设计原则
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那你就可以确定,这部分代码需要被抽出来,和其他稳定的代码有所区分,把变化的部分封装
起来,以便以后可以轻易的改动或扩展此部分,而不影响不需要变化的部分。
从哪里开始呢?我们知道Chicken
类的sayHi()
方法和run()
方法会随着鸡的不同而改变,比如尖叫鸡sayHi()
的方式就和别的鸡不一样,它也不会跑。根据上面说的设计原则
,需要将它们独立出来,为了把这两个行为从Chicken
类中分开,程序员将它们从Chicken
类中取出来,建立一组新类来代表每个行为。示意图如下:
现在已经把变化和不会变化的部分分开了,需要考虑如何设计鸡的行为类了。继续翻看编程指南,发现了另一个设计原则。
设计原则
针对接口编程,而不是针对实现编程。
现在程序员利用接口代表每个行为,比如,SayHiBehavior
和RunBehavior
,而行为的每个实现都将实现其中的一个接口。我们来看一下这两个接口的代码:
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() {
// 什么也不做,不会跑
}
}
一切准备就绪,我们该开始重构了。
父类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()
方法将具体的打招呼行为和跑步行为委托给SayHiBehavior
和RunBehavior
去做。
测试一下,测试方法长这样:
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();
}
}
我们给白鸡设置了普通打招呼策略(算法)
,给尖叫鸡设置的是惨叫打招呼策略(算法)
,然后给白鸡设置了正常跑步策略(算法)
,给尖叫鸡设置的是不会跑步策略(算法)
。
执行结果:
完美的实现了策略模式,在运行时想改变鸡的行为,只需要调用setter
方法就行了。
拿出随身携带的镜子照一下,帅呆了~,叼就一个字,我说一万次!
以上就是策略模式了,定义了算法族,分别封装起来,让他们之间可以互相替换。比如鸡的打招呼行为就有普通打招呼和惨叫打招呼,想用哪个就用哪个。在这里也使用了像多态
和组合
来辅助实现策略模式。多用组合,少用继承也是一个设计原则。