DP_1_strategy_鸭子

Joe开发一种可以模拟展示多种会游泳和呷呷叫的鸭子的游戏。系统里所有鸭子都继承于Duck基类,系统核心类图如下:
这里写图片描述
Duck基类里实现公共的quack()和swim(),而MallardDuck和RedheadDuck可分别覆盖实现自己的display(),即重用了公共部分,又支持不同子类的个性化扩展。很好的设计哈!
但是,公司要让鸭子飞起来。Joe发现只要在Duck里增加一个fly()就可搞定,这样所有继承Duck的鸭子就都拥有会飞的能力!改进后的系统类图如下:
这里写图片描述
此时又要求RubberDuck不能飞(Duck类里增加的方法,也同样被继承于Duck的RubberDuck类继承了,所以就有了会飞的橡皮鸭子),Joe想到:如果在RubberDuck类里重写fly(),让RubberDuck什么都不做即可,但以后再增加一个木头鸭子呢?它不会飞也不会叫,还要再重写quack()和fly()方法,太麻烦且混乱。
Joe认识到使用继承不是办法(每月都要升级且变化频繁),如果以后靠逐个类去判断是否重写了quack()或fly()来应对变化,显然混不下去!
为什么屡试不爽的继承,在系统维护升级时,无法很好地支持重用呢?
使用接口怎么样?可以把fly()放在接口里,只有那些会飞的鸭子才需要实现这个接口,把quack()也放到一个接口里,因为有些鸭子不会叫:
这里写图片描述
//注意:上图RubberDuck类的quack()应该实现的是Quackable接口.
但以后所有需要quack()和fly()的鸭子都去重复实现这两个方法的功能,有几十、上百个鸭子时怎么办?某个方法要做一点修改,难道要重复修改上百遍?
并不是所有的鸭子都会飞、会叫,所以继承不是正确的方法。虽然使用Flyable接口方法可解决部分问题(不再有会飞的橡皮鸭子),但这彻底破坏了重用,它带来了另一个维护的噩梦,而且还有一个问题,难道所有鸭子的飞行方式、叫声等行为都是一模一样的吗?
回顾面向对象设计原则。计划没有变化快,所以直面“变化这个事实”才是正道!Joe面对的问题是,鸭子的行为在子类里持续不断地改变,所以让所有的子类都拥有基类的行为是不适当的,而使用上面的接口的方式,又破坏了代码重用。第一个设计原则:
Identify the aspects of your application that vary and separate them from what stays the same.(找到系统中变化的部分,将变化部分同其它稳定部分隔开)
找到变化并且把它封装起来,以后就可以在不影响其它部分的情况下修改或扩展被封装的变化部分。它几乎是所有设计模式的基础,所有模式都提供了使系统里变化部分独立于其它部分的方法。
鸭子问题,变化部分就是子类里的行为。所以要把这部分行为封装起来,目前就是fly()和quack()行为,而swim()行为很稳定,可用继承来实现代码重用,所以把fly()和quack()行为从Duck基类里隔离出来。要创建两组不同的行为,一组表示fly()行为,一组表示quack()行为。为什么是两组而不是两个呢?因为对于不同子类来说,fly()和quack()的表现形式都是不一样,有的嘎嘎叫,有的呷呷叫。有了这两组行为,就可以组合出不同的鸭子,例如:要实例化MallardDuck(野鸭)实例,并且初始化一个特殊类型的飞行行为(飞行能力强)。更进一步,可以动态地改变一个鸭子的行为,将在Duck类里包含行为设置方法,所以在运行时改变MallardDuck的飞行行为,怎么做?第二个设计原则:
Program to an interface, not an implementation.
(面向接口编程,而不要面向实现编程)
“接口”这个词已被赋予太多含义,一点儿屁事就“接口”。这里的接口是一个抽象的概念,不局限于语言层面的接口(interface)。接口可以是抽象类/基类,因为基类变量可以用来引用其子类。要点在于,在面向接口编程时,可用多态,那么实际运行的代码只依赖于具体接口(interface/抽象类/基类),而不管这些接口提供的功能是如何实现的,也就是说,接口将系统的不同部分隔离开来,同时又将它们连接在一起
根据面向接口编程的设计原则,应该用接口来隔离鸭子问题中变化部分,也就是鸭子的不稳定行为(fly()、quack())。用FlyBehavior接口表示鸭子的飞行行为,可有多种实现方式,“横”飞“竖”飞,好处就是将鸭子的行为实现在一组独立的类里,具体的鸭子是通过FlyBehavior接口来调用这个行为的,因为Duck只依赖FlyBehavior接口,所以不需管FlyBehavior是如何被实现的,FlyBehavior和QuackBehavior接口都有不同的实现方式!
这里写图片描述
1>给Duck类增加两个接口类型的实例变量flyBehavior和quackBehavior,就是新的设计里的“飞行”和“叫唤”行为。每个鸭子对象会使用各种方式来设置这些变量,以引用它们期望的运行时的特殊行为类型(横着飞,吱吱叫等)。
2>把fly()和quack()从Duck类里移除,使用两个相似的PerformFly()和PerformQuack()来替换fly()和qucak()
3>初始化flyBehavior和quackBehavior变量。最简单就是在Duck类初始化同时初始化他们。但更好办法是提供两个可以动态设置变量值的方法SetFlyBehavior()和SetQuackBehavior(),可在运行时动态改变鸭子的行为。
这里写图片描述
代码实现:
The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
(策略模式定义了一系列算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化)
这里写图片描述

  • Context(应用场景):
    需要使用ConcreteStrategy提供的算法。
    内部维护一个Strategy实例。
    负责动态设置运行时Strategy具体的实现算法。
    负责跟Strategy之间交互和数据传递。
  • Strategy(抽象策略类):
    定义的公共接口,不同算法以不同方式实现这个接口,Context使用这个接口调用不同算法,一般使用接口/抽象类实现。
  • ConcreteStrategy(具体策略类):
    实现Strategy定义的接口,提供具体的算法实现。

Strategy优缺点: //每一个模式只有在特定场景下才能发挥功效。
Strategy主要应用场景:

  • 多个类只区别在表现行为不同,在运行时动态选择具体要执行的行为。(eg>FlyBehavior/QuackBehavior)
  • 需要在不同情况下使用不同的策略(算法),或者策略可能在未来用其它方式来实现。(eg>FlyBehavior/QuackBehavior的具体实现可任意变化)
  • 对客户(Duck)隐藏具体策略(算法)的实现细节,彼此完全独立。

Strategy主要优点:

  • 提供了一种替代继承的方法,而且既保持了继承的优点(代码重用)还比继承更灵活(算法独立,可以任意扩展)。
  • 避免程序中使用多重条件转移语句,使系统更灵活,并易于扩展。
  • 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。

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

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

//.net实例,可以overlook:
ArrayList类的Sort()定义:
public virtual void Sort (IComparer comparer)
Sort()接收一个IComparer类型的参数,IComparer接口是做什么用的呢?
ArrayList相当于Strategy模式中Context(应用场景)部分,而IComparer相当于Strategy(抽象策略类)部分,myReverserClass相当于ConcreteStrategy(具体策略类)部分。只要知道这是一个具体策略类,它提供了应用场景需要的具体算法,实现了抽象策略类接口,而应用场景通过抽象策略类动态调用到了具体策略类中的算法。典型Strategy。
还可以提供很多自定义的具体策略类的实现,只要这些类实现了IComparer接口,就可在运行时动态设置给ArrayList类的Sort(),在Sort()中会根据具体策略类实现的比较算法规则来对ArrayList中的数据进行排序。

高层次的东西:设计原则
Strategy模式不仅保留了继承的优点,而且还提供了更灵活的扩展能力
因为它“上面有人”啊!谁啊?
Favor composition over inheritance.
(优先使用对象组合,而非类继承)
关于组合和继承:组合是一种”has a”关系,而继承是一种”is a”关系。”has a”比”is a”更灵活。
在创建系统时,应该优先使用对象组合,因为它不仅可以提供更多灵活性和扩展性,而且还可以在运行时改变行为(组合不同的对象)
继承应该用在稳定的地方,eg>Duck类里的Swim(),因为可以肯定所有鸭子都会游泳,所以没必要给这个行为提供基于Strategy的实现方式,因为那样做程序更复杂,没意义。

出处:http://justinw.cnblogs.com/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值