设计模式之一:策略模式(Strategy pattern)

来源:互联网 发布:西南理工大学网络教育 编辑:程序博客网 时间:2024/05/21 15:05

设计模式之一:策略模式(Strategy pattern)

定义:

定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

The strategy pattern definesa family of algorithms, encapsulates each one, and makes them interchangeable.Strategy lets the algorithm vary independently from clients that use it.

要点:

1.       知道OO基础,并不足以让你设计出良好的OO系统。

2.       良好的OO设计必须具备可复用、可扩充、可维护三个特性。

3.       模式可以让我们建造出具有良好OO设计质量的系统。

4.       模式被认为是历经验证的OO设计经验。

5.       模式不是代码,而是针对设计问题的通用解决方案。你可以把它们应用到特定的应用中。

6.       模式不是被发明,而是被发现。

7.       大多数的模式和原则,都着眼于软件变化的主题。

8.       大多数的模式都允许系统局部改变独立于其他部分。

9.       我们常把系统中会变化的部分抽出来封装。

10.  模式让开发人员之间有共享的语言,能够最大化沟通的价值。

模式介绍

策略模式是一个很简单的模式,也是一个很常用的模式,可谓短小精悍。废话不多说了,下面开始介绍策略模式。

Joe的公司做了一套相当成功的模拟鸭子游戏:SimUDuck.游戏中会出现各种鸭子,一边游泳戏水,一遍呱呱叫。系统的核心类图如下所示:

如图所示,在Duck基类里实现了公共的quack()和swim()方法,而MallardDuck和RedheadDuck可以分别覆盖实现自己的display()方法,这样既重用了公共的部分,又支持了不同子类的个性化扩展。

随着公司竞争压力的加剧,公司主管决定设计出会飞的鸭子来将竞争对手抛在后头。Joe拍着胸脯保证一个星期就可以解决。Joe发现只要在Duck类中加上fly()方法,然后所有鸭子都会继承fly().

Joe很高兴的带着自己的产品到股东会议上去展示,有很多“橡皮鸭子”飞来飞去。这是怎么回事?原来Joe忽略了一件事:并非Duck所有的子类都会飞。Joe在Duck超类中加上新的行为,会使得某些并不适合该行为的子类 也具有该行为。现在可好了!SimUDuck程序中有了一个无生命的会飞的东西。他意会到了一件事:当涉及“维护”时,为了“复用”目的而使用继承,结局并不完美。Joe很郁闷!他突然想到:如果在RubberDuck类里把fly()方法重写一下会如何?在RubberDuck类的fly()里让橡皮鸭子什么都不做,不就一切OK了吗!那以后再增加一个木头鸭子呢?它不会飞也不会叫,那不是要再重写quack()和fly()方法,以后再增加其它特殊的鸭子都要这样,这不是太麻烦了,而且也很混乱。

最终,Joe认识到使用继承不是办法,因为他的上司通知他,董事会决定以后每6个月就会升级一次系统,以应对市场竞争,所以未来的变化会很频繁,而且还不可预知。如果以后靠逐个类去判断是否重写了quack()或fly()方法来应对变化,显然混不下去!

那么用接口能不能解决这个问题吗?把fly()从超类中取出来,放进一个“Flyable接口”中。这么一来只有会飞的鸭子才实习该接口。同样的方式,也可以用来设计一个“Quackable接口”,因为不是所以的鸭子都会叫。

但是这种方法会出现代码无法重用的问题,如果鸭子的类特别多的话,就这么几个鸭子还好说,但是我们有几十、上百个鸭子的时候你怎么办?如果某个方法要做一点修改,就需要重复修改上百遍。

呵呵!如果你是Joe,你该怎么办?

我们知道,并不是所有的鸭子都会飞、会叫,所以继承不是正确的方法。但是虽然上面的使用Flyable接口的方法,可以解决部分问题(不再有会飞的橡皮鸭子),但是这个解决方案却彻底破坏了重用,它带来了另一个维护的噩梦!而且还有一个问题我们前面没有提到,难道所有的鸭子的飞行方式、叫声等行为都是一模一样的吗?不可能吧!

说到这里,为了能帮助Joe摆脱困境,我们有必要先停下来,重新回顾一些面向对象设计原则。请您告诉我:“什么东西是在软件开发过程中是恒定不变的?”,您想到了吗?对,那就是变化本身,正所谓“计划没有变化快”,所以直面“变化这个事实”才是正道!Joe面对的问题是,鸭子的行为在子类里持续不断地改变,所以让所有的子类都拥有基类的行为是不适当的,而使用上面的接口的方式,又破坏了代码重用。现在就需要用到我们的第一个设计原则:

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

换句话说,如果每次心的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他文档的代码有所区分。这个原则的另一种思考方式是:把会变化的部分取出并封装起来,以便以后可以轻易地盖栋或扩充此部分,而不影响不需要变化的其他部分。

OK!现在我们已经有了一条设计原则,那么Joe的问题怎么办呢?就鸭子的问题来说,变化的部分就是子类里的行为。所以我们要把这部分行为封装起来,省得它们老惹麻烦!从目前的情况看,就是fly()和quack()行为总是不老实,而swim()行为是很稳定的,这个行为是可以使用继承来实现代码重用的,所以,我们需要做的就是把fly()和quack()行为从Duck基类里隔离出来。我们需要创建两组不同的行为,一组表示fly()行为,一组表示quack()行为。为什么是两组而不是两个呢?因为对于不同的子类来说,fly()和quack()的表现形式都是不一样的,有的鸭子嘎嘎叫,有的却呷呷叫。有了这两组行为,我们就可以组合出不同的鸭子,例如:我们可能想要实例化一个新的MallardDuck(野鸭)实例,并且给它初始化一个特殊类型的飞行行为(野鸭飞行能力比较强)。那么,如果我们可以这样,更进一步,为什么我们不可以动态地改变一个鸭子的行为呢?换句话说,我们将在Duck类里包含行为设置方法,所以我们可以说在运行时改变MallardDuck的飞行行为,这听起来更酷更灵活了!那么我们到底要怎么做呢?回答这个问题,先要看一下我们的第二个设计原则:

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

针对接口编程真正的意思是针对超类型编程。“针对接口编程”关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被蚌寺在超类型的行为上。“针对超类型编程”这句话,可以更明确地说成“变量的生命类型应该是超类型,通常是一个抽象类或者是一个接口,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不用理会以后执行时的真正对象类型”

根据面向接口编程的设计原则,我们应该用接口来隔离鸭子问题中变化的部分,也就是鸭子的不稳定的行为(fly()、quack())。

第一步:我们要给Duck类增加两个接口类型的实例变量,分别是flyBehavior和quackBehavior,它们其实就是新的设计里的“飞行”和“叫唤”行为。每个鸭子对象都将会使用各种方式来设置这些变量,以引用它们期望的运行时的特殊行为类型(使用横着飞,吱吱叫,等等)。

第二步:我们还要把fly()和quack()方法从Duck类里移除,因为我们已经把这些行为移到FlyBehavior和QuackBehavior接口里了。我们将使用两个相似的PerformFly()和PerformQuack()方法来替换fly()和qucak()方法,后面你会看到这两个新方法是如何起作用的。

第三步:我们要考虑什么时候初始化flyBehavior和quackBehavior变量。最简单的办法就是在Duck类初始化的时候同时初始化他们。但是我们这里还有更好的办法,就是提供两个可以动态设置变量值的方法SetFlyBehavior()和SetQuackBehavior(),那么就可以在运行时动态改变鸭子的行为了。

修改后的Duck类如下图所示:


测试代码:

public abstract class Duck {FlyBehavior flyBehavior;QuackBehavior quackBehavior;public Duck(){}public void performFly(){flyBehavior.fly();}public void performQuack(){quackBehavior.quack();}public void swim(){System.out.println("All ducks float,even decoys!");}public void setFlyBehavior(FlyBehavior fb){flyBehavior = fb;}public void setQuackBehavior(QuackBehavior qb){quackBehavior = qb;}}

public class ModelDuck extends Duck{public ModelDuck(){flyBehavior = new FlyNoWay();quackBehavior = new Quack();}public void display(){System.out.println("I'm a model duck");}}

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 MuteQuack implements QuackBehavior {@Overridepublic void quack() {// TODO Auto-generated method stubSystem.out.println("<<Silence>>");}}

public interface FlyBehavior {public void fly();}

public class FlyNoWay implements FlyBehavior {public void fly(){System.out.println("I can't fly");}}

public class FlyRocketPowered implements FlyBehavior {@Overridepublic void fly() {// TODO Auto-generated method stubSystem.out.println("I'm flying with a rocket!");}}

public class FlyWithWings implements FlyBehavior {public void fly(){System.out.println("I'm flying");}}

public interface QuackBehavior {public void quack();}

public class Quack implements QuackBehavior {@Overridepublic void quack() {// TODO Auto-generated method stubSystem.out.println("Quack");}}

public class Squeak implements QuackBehavior {@Overridepublic void quack() {// TODO Auto-generated method stubSystem.out.println("Squeak");}}

public class MiniDuckSimulator {public static void main(String[] args){Duck mallard = new MallardDuck();mallard.performQuack();mallard.performFly();Duck model = new ModelDuck();model.performQuack();model.performFly();model.setFlyBehavior(new FlyRocketPowered());model.performFly();}}

程序运行结果如下:

应用场景和优缺点

上面我们已经看过了Strategy模式的详细介绍,下面我们再来简单说说这个模式的优缺点吧!怎么说呢,人无完人,设计模式也不是万能的,每一个模式都有它的使命,也就是说只有在特定的场景下才能发挥其功效。我们要使用好模式,就必须熟知各个模式的应用场景。

对于Strategy模式来说,主要有这些应用场景:

1、  多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。(例如FlyBehavior和QuackBehavior)

2、  需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。(例如FlyBehavior和QuackBehavior的具体实现可任意变化或扩充)

3、  对客户(Duck)隐藏具体策略(算法)的实现细节,彼此完全独立。

 

对于Strategy模式来说,主要有如下优点:

1、  提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)。

2、  避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。

3、  遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。

对于Strategy模式来说,主要有如下缺点:

1、  因为每个具体策略类都会产生一个新类,所以会增加系统需要维护的类的数量。

封装行为的大局观

我们已经深入研究了鸭子模拟器的设计,概述将头探出水面,呼吸空气的时候了,先来就来看看整体的格局。
下面是整个重新设计后的类结构,你所期望的一切都有:鸭子继承Duck,飞行行为实现FlyBehavior接口,呱呱叫行为实现QuackBehavior接口。类图如下图所示:

最后一个设计原则:多用组合,少用继承。
将两个类结合起来使用,如同本例一般,这就是组合。这种红作法和“集成”不同的地方在于,鸭子的行为不是继承来的,而是和适当的行为对象“组合”来的。使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 银行工作人员借钱不还怎么办 借钱不还跑了但有工作怎么办 亲戚家借钱不还怎么办 学习瑜伽教练口令好复杂怎么办 练瑜伽腿的柔韧性不够怎么办 瑜伽扭转时手抓不到脚怎么办 练瑜伽腿部太硬怎么办 褶皱衣服不紧了怎么办 吃撑了肚子涨怎么办 正好压本科线该怎么办 大脚趾被砸了怎么办 脊柱侧弯每天疼怎么办 内衣扣的位置脊柱疼怎么办 练完瑜伽颈椎疼怎么办 乳胶枕头太高了怎么办 枕头太高脖子痛怎么办 颈椎突出症状缓解后怎么办 外痔疼的的历害怎么办 小肚子疼得历害怎么办 练瑜伽伤到颈椎怎么办 鼻子干口干胃烧怎么办 颈椎病压迫神经引起手麻怎么办 4个月婴儿睡觉少怎么办 晚上睡觉睡不好老是醒来怎么办 吃了没熟的香蕉怎么办 70岁父母老吵架怎么办 怀孕七个月晚上睡不着怎么办 九个月宝宝睡眠不好怎么办 一个月的宝宝放不下怎么办 宝宝被吓了发烧怎么办 体内火气重睡不着觉怎么办 宝宝睡觉一直翻身发出声音怎么办 严重失眠怎么办整夜睡不着觉 腿疼得睡不着觉怎么办 19岁晚上睡不着该怎么办 工作累的想哭怎么办 心累迷茫想哭怎么办 白天很累晚上又睡不着怎么办 发型睡觉压乱了怎么办 通宵一夜第二天怎么办 夏天了腿脚还凉怎么办