设计模式之策略模式(最基层的模式)
序头:此篇文章在电脑旁边老老实实的写了三个小时,我太难了
咱们先从简单的模拟鸭子应用做起
上班的公司做了一套相当成功的模拟鸭子游戏: 游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。此系统的内部设计
使用了标准的OO技术,设计了一个鸭子超类(Duck),并让各种鸭子继承此超类。
好景不长,公司的竞争压力加剧,在为期一周的高尔夫假期兼头脑风暴会议之后,公司主管认为该是创新的时候了,他们需要在下周的“”贸易岛“”股东会议上展示一些“”“真正”
让人印象深刻的东西来振奋人心,主管们确定,此模拟程序需要会飞的鸭子来将竞争者抛在后头。当然,在这个时候,张小牛拍胸告诉主管们,只需要一个星期就可以搞定。
他说不就小菜一碟吗,我只需要在Duck类中加上fly()飞行方法,然后所有的鸭子都会继承fly()飞行方法,这样就实现了会飞的鸭子。这是我大显身手,展示OO才华的时候了。
但是,可怕的问题发生了.......
老板打电话火冒三丈的告诉张小牛,他在股东会议上,刚刚看了一下展示,有很多“橡皮鸭子”在屏幕上飞来飞去,这是你在开玩笑吗?你可能需要去逛逛BOOS直聘、前程无忧了........
怎么回事?why?
老牛突然想起忽略了一件事,并非继承Duck类所有的子类都需要飞。在Duck超类中加上新的行为,会使得某些并不适合该行为的子类也具有该行为。现在可好了!
程序中有了一个无生命会飞的东西。
对代码所做的局部修改,影响层面可不只是局部(会飞的橡皮鸭!)
利用接口如何?
小牛认识到继承可能不是答案,因为他刚刚拿到来自主管的备忘录,希望以后每六个月更新产品。
这次他灵光一现,我可以把fly()方法从超类中取出来,放进一个“Flyable”接口中。这么一来,只有会飞的鸭子才实现此接口。同样的方式,再定义一个“Quackble”接口,因为不是所有的鸭子都会叫。 哇塞!我真是太聪明了,问题解决
旁边一秃头小伙侃侃而谈,这真是一个超笨的主意,如果每一个鸭子都要单独去实现接口,那就得给每一个鸭子都需要写实现接口的方法,你没发现这么一来重复的代码会变多吗?,假设有100只鸭子,就得编写100次,再加上Quackble接口,加起来是200次,还有其他........
把问题归零........
现在我们知道使用继承并不能很好的解决问题,因为鸭子的行为在子类里不断地改变,并且让所有的子类都有这些行为是不恰当的。Flyable接口和Quackble接口单独拿出来拿出来让需要的鸭子去实现,似乎一开始还不错,解决了问题(只有会飞的鸭子才继承Flyable接口),但是Java接口不具有实现代码,所有继承接口无法达到代码的复用。
这意味着:无论何时你需要修改某个行为,你必须得往下追踪并在每一个定义此行为的类中修改它,一不小心,可能会造成新的错误!
幸运的是,有一个设计原则,巧合适用于此状况。
设计原则
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。
这个原则的另一种思考方式:“把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分”。
这样的概念很简单,几乎是每个设计模式背后的精神所做。所有的模式都提供了一套方法让“系统中的某部分改变不会影响其他部分”。
获取了秘技,咱们再重新回到鸭子设计游戏中
现在,为了要分开“变化和不会变化的部分”,我们准备建立两组类(完全远离Duck子类),一个是“fly”相关的,一个是“quack”相关的。每一组类将实现各自的动作。
比方说fly接口类,可能有一个类实现“呱呱叫”,另一个类实现“吱吱叫”,还有一个类实现“安静”。
我们希望一切能有弹性,毕竟,正是因为一开始鸭子行为没有弹性,才让我们走上现在这条路。我们还想能够“指定”行为到鸭子的实例。比方说,
我们想要产生一个新的绿头鸭实例,并指定特定“类型”的飞行行为给它,干脆顺便让鸭子的行为可以动态的改变好了,换句话说,我们应该在鸭子类中包含设定行为的方法,
这样就可以在“运行时”动态地“改变”绿头鸭的飞行行为。
我们利用接口代表每个行为,这次鸭子类不会负责实现Fly和Quack接口,反而是由我们制造一组其他类专门Flyable接口与Quackable接口,这就称为“行为类”。
由行为类而不是Duck类来实现行为接口。
这样的做法不同于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。这两种做法都是依赖于“实现”,我们被实现绑得死死的,没
办法更改行为,除非写更多代码。
整合鸭子的行为
关键在于,鸭子现在会将飞行和呱呱叫的动作交给别人来处理,而不是鸭子本身来继承或实现。
做法是这样的
首先,在Duck类中“加入两个实例变量”,分别为:“flyBehaior”和“quackBehavior”,声明为接口类型(而不是具体的实现类型),每个鸭子对象都会动态地设置这些
变量以在“运行时引用”正确的行为类型。
public class Duck { FlyBehaior flyBehaior ; // 飞行实例接口 QuackBehaior quackBehaior ; // 叫实例接口 public Duck(){ }
// 子类如果继承Duck类,则会自动继承此方法,就可以在运行中动态实例化一个飞行实例
public void setFlyBehaior(FlyBehaior fly){
this.flyBehaior = fly ;
}
// 子类如果继承Duck类,则会自动继承此方法,就可以在运行中动态实例化一个叫实例
public void setQuackBehaior(QuackBehaior quack){
this.quackBehaior = quack ;
}
// 鸭子的其他方法.....
}
我们写一个FlyBehavior接口和它的两个实现类,由于QuackBehavior接口写法和它是同理,就不写它了,大家有兴趣自己可以写。
// 定义FlyBehavior接口 public interface FlyBehavior{ // 定义飞行方法 public void fly(); } // 两个实现类,实现类可以有无数个 public class FlyWithWing implements FlyBehavior{ // 实现该方法 public void fly(){ system.out.println("会飞的鸭子"); } } public class FlyNoWay implements FlyBehavior{ // 实现该方法 public void fly(){ system.out.println("不会飞的鸭子"); } }
现在我们在写一个鸭子的子类继承Duck
public class ModelDuck extends Duck{ // 你看,这个类我们什么都可以不写就能实现飞行和叫的行为 }
看了这么多,写了这么多,是时候收割成果的时候了,我们写一个测试运行类,来证明咱们是真懂了
public class Test{ public static void main(String[] args){ // 先随便上只会飞的鸭子行为实例 FlyBehavior flyb = new FlyWithWings(); // 紧接着咱们再实例化一只鸭子 Duck duck = new ModelDuck(); // 现在咱们就可以给这只鸭子动态设置它的飞行行为了 duck.setFlyBehavior(flyb); // 运行结果展示 控制台输出..... 会飞的鸭子 // 咱们还在这里把它动态改变成不会飞的鸭子,只需要实例化一个不会飞的实例 FlyBehavior no = new FlyNoWay(); // 通过设置方法加载进去 duck.setFlyBehavior(no); // 运行结果展示 控制台输出..... 不会飞的鸭子 } }
至此,我们已经深入研究了鸭子模拟器的一步步优化设计,该是将头探出水面,呼吸空气的时候了。
到这来,我们就使用了设计模式中最基层的模式:也就是策略模式
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,
此模式让算法的变化独立于使用算法的客户。

浙公网安备 33010602011771号