面向对象的程序员不能不知道一些设计模式,熟悉设计模式,可以使我们的代码更加优雅,易于维护和交流,下面进入正题。
让我们从一个例子开始策略模式的探索之旅吧!
背景:写一个模拟鸭子的游戏,游戏中有各种鸭子,一边戏水,一边叫。
开工
首先您想到继承,一个鸭子的超类,让各种鸭子继承此超类,就像下面的图所示:
似乎可以了,然后需求又来来了,大家都懂得,对于程序猿,需求的改变是最蛋碎的了。
我希望鸭子会飞,然后就想到了下面的设计:
好了,恐怖的事情发生了:假如我在游戏中生产了一个橡皮鸭,然后。。。看到它们在飞来飞去,不是傻子都知道这怎么可能呢!
下面是加了橡皮鸭的图:
好了,那我就随机应变吧,在橡皮鸭种把fly方法覆盖掉,什么也不做:
。。。不久之后,噩梦又来了,游戏要加一个木头鸭,俗称诱饵鸭(DecoyDuck),它不会飞,也不会叫。。。
像这样的需求每次添加一个新类型的鸭子就要检查是否要覆盖超类的方法,即使覆盖了,什么也不做,是否觉得浪费国家资源啊!果断放弃吧,亲!
纠结好久好,想到了接口,脑袋里冒出个下面的想法:
就像下面的图所展示的那样
好了,你的一个很喜欢刁难人的上司来看了你的设计,并给出了以下结论:
下面来总结一下问题所在:
如上分析我们不能通过继承很好滴解决问题,Flyable和Quackable接口一开始似乎不错,但是java接口不具有实现代码,所以继承接口无法达到代码复用
。这意味着:无论何事需要修改某个行为,都必须追踪并在每一个定义次行为的类中修改它,一不小心,就可能造成新的错误。
幸运的是有女神来了。。。OO中的一个设计原则:
找出应用中可能需要变化之处,把它们独立出来,不要和那些不变的代码混在一起
让我们用上面的原则来分析这个鸭子游戏吧:
我们知道Duck类的fly()和quack()会随着鸭子的不同而改变
所以,我们把这两个行为抽取出来,建立一组新类代表每个行为。如下图所示:
那么如何实现飞行和呱呱叫的行为呢?
也许你想设计更具有弹性:可以给新产生的鸭子实例指定行为;可以运行时动态改变鸭子的行为。
上面的要求似乎有点抽象,它体现了OO中另外一个设计原则:
针对接口编程,而不是针对实现编程。
注:针对接口编程,也就是多态的运用,运行时决定具体行为
下面是具体设计:
我们利用接口代表每个行为,比如:FlyBehavior与QuackBehavior,而具体行为则实现其中的一个接口。
这次鸭子类不会负责实现Flyable和Quackable接口,而是由我们制造出其他类专门实现FlyBehavior与QuackBehavior接口。这些类称为”行为“类。
和以往不同,以前:行为来自Duck超类的具体实现,或是继承某个接口并由子类实现。这两种做法都依赖具体“实现”,我们被实现绑得死死地,没办法更改行为,
除非在子类中该更多代码。
下面是鸭子行为的实现图:
这样的设计:可以让飞行和呱呱叫行为被其他的对象复用,而且这些行为已经
与鸭子类无关了。
而且我们可以新增一些行为,不会影响到既有鸭子的行为类,也不会影响“使用”到
飞行行为的鸭子类。
下面让我们来整合鸭子的行为到Duck类吧
关键是:鸭子现在会将飞行和呱呱叫行为和飞行行为“委托”给别人处理,而不是使用定义在Duck类(或子类)内的呱呱叫和飞行方法。
下面是重构后的Duck:
我们可以在Duck子类的构造函数中初始化flyBehavior和quackBehavior变量,但是这明显是针对实现的编程,因为我们在构造函数中创建了一个行为对象,
很好的做法是,我们在Duck超类中添加一个方法比如:setFlyBehavior(FlyBehavior fb)和setQuackBehavior(QuackBehavior qb)
这样我们就可以实现前面说的运行时动态改变行为了。
至此全部分析完毕:下面是类的整体图
这里又引出一个OO设计原则:多用组合,少用继承。
组合建立的系统据有很大弹性,不仅可将算法封装成类,更可以“运行时动态改变行为”,只要组合的行为对象符合正确接口标准即可
最后,让我奉上策略模式的定义吧:
定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
此文章内容是我从《Head First设计模式》一书中提炼出来的,大家有兴趣可以参阅此书,网上还有各个设计模式的代码,这里我就不粘贴了。
感谢捧场!