设计模式解读之一: 策略模式

1. 模式定义

把会变化的内容取出并封装起来,以便以后可以轻易地改动或扩充部分,而不影响不需要变化的其他部分;

2.
问题缘起
当涉及至代码维护时,为了复用目的而使用继承,结局并不完美。对父类的修改,会影响到子类型。在超类中增加的方法,会导致子类型有该方法,甚至连那些不该具备该方法的子类型也无法免除。示例,一个鸭子类型:

public
abstract class Duck {
    //所有的鸭子均会叫以及游泳,所以父类中处理这部分代码
    public void
quack() {
        System.out.println("Quack");
    }

public
void swim() {
        System.out.println("All ducks float, even
decoys.");      
    }

//因为每种鸭子的外观是不同的,所以父类中该方法是抽象的,由子类型自己完成。
    public abstract void
display();
}

public class MallardDuck extends Duck {
   
//野鸭外观显示为绿头
    public void display() {
        System.out.println("Green
head.");
    }
}

public class RedHeadDuck extends Duck {
   
//红头鸭显示为红头
    public void display() {
        System.out.println("Red
head.");
    }
}

public class RubberDuck extends Duck {

//橡皮鸭叫声为吱吱叫,所以重写父类以改写行为
    public void quack() {
       
System.out.println("Squeak");
    }

//橡皮鸭显示为黄头
    public void
display() {
        System.out.println("Yellow head.");
   
}
}

上述代码,初始实现得非常好。现在我们如果给Duck.java中加入fly()方法的话,那么在子类型中均有了该方法,于是我们看到了会飞的橡皮鸭子,你看过吗?当然,我们可以在子类中通过空实现重写该方法以解决该方法对于子类型的影响。但是父类中再增加其它的方法呢?

通过继承在父类中提供行为,会导致以下缺点:

a.
代码在多个子类中重复;
b. 运行时的行为不容易改变;
c.
改变会牵一发动全身,造成部分子类型不想要的改变;

好啦,还是刚才鸭子的例子,你也许想到使用接口,将飞的行为、叫的行为定义为接口,然后让Duck的各种子类型实现这些接口。这时侯代码类似于:

public
abstract class Duck {
    //将变化的行为 fly()
以及quake()从Duck类中分离出去定义形成接口,有需求的子类中自行去实现

public void swim() {
     
  System.out.println("All ducks float, even decoys.");      
    }

public abstract void display();
}

//变化的 fly() 行为定义形成的接口
public
interface FlyBehavior {
    void fly();
}

//变化的 quack()
行为定义形成的接口
public interface QuackBehavior {
    void
quack();
}

//野鸭子会飞以及叫,所以实现接口  FlyBehavior, QuackBehavior
public
class MallardDuck extends Duck implements FlyBehavior, QuackBehavior{
   
public void display() {
        System.out.println("Green head.");
   
}

public void fly() {
       
System.out.println("Fly.");              
    }

public void
quack() {
        System.out.println("Quack.");              
   
}
}

//红头鸭子会飞以及叫,所以也实现接口  FlyBehavior, QuackBehavior
public class
RedHeadDuck extends Duck implements FlyBehavior, QuackBehavior{
    public
void display() {
        System.out.println("Red head.");
    }

public void fly() {
       
System.out.println("Fly.");              
    }

public void
quack() {
        System.out.println("Quack.");              
    }

}

//橡皮鸭不会飞,但会吱吱叫,所以只实现接口QuackBehavior
public class RubberDuck
extends Duck implements QuackBehavior{
    //橡皮鸭叫声为吱吱叫
    public void
quack() {
        System.out.println("Squeak");
    }

//橡皮鸭显示为黄头
    public void display() {
        System.out.println("Yellow
head.");
    }
}

上述代码虽然解决了一部分问题,让子类型可以有选择地提供一些行为(例如 fly()
方法将不会出现在橡皮鸭中).但我们也看到,野鸭子MallardDuck.java和红头鸭子RedHeadDuck.java的一些相同行为代码不能得到重复使用。很大程度上这是从一个火坑跳到另一个火坑。

在一段程序之后,让我们从细节中跳出来,关注一些共性问题。不管使用什么语言,构建什么应用,在软件开发上,一直伴随着的不变的真理是:需要一直在变化。不管当初软件设计得多好,一段时间之后,总是需要成长与改变,否则软件就会死亡。
 
 
我们知道,继承在某种程度上可以实现代码重用,但是父类(例如鸭子类Duck)的行为在子类型中是不断变化的,让所有子类型都有这些行为是不恰当的。我们可以将这些行为定义为接口,让Duck的各种子类型去实现,但接口不具有实现代码,所以实现接口无法达到代码复用。这意味着,当我们需要修改某个行为,必须往下追踪并在每一个定义此行为的类中修改它,一不小心,会造成新的错误。

设计原则:把应用中变化的地方独立出来,不要和那些不需要变化的代码混在一起。这样代码变化引起的不经意后果变少,系统变得更有弹性。

按照上述设计原则,我们重新审视之前的Duck代码。

1)
分开变化的内容和不变的内容

Duck类中的行为 fly(), quack(),
每个子类型可能有自己特有的表现,这就是所谓的变化的内容。
Duck类中的行为 swim()
每个子类型的表现均相同,这就是所谓不变的内容。

我们将变化的内容从Duck()类中剥离出来单独定义形成接口以及一系列的实现类型。将变化的内容定义形成接口可实现变化内容和不变内容的剥离。其实现类型可实现变化内容的重用。这些实现类并非Duck.java的子类型,而是专门的一组实现类,称之为"行为类"。由行为类而不是Duck.java的子类型来实现接口。这样,才能保证变化的行为独立于不变的内容。于是我们有:

变化的内容:

//变化的
fly() 行为定义形成的接口
public interface FlyBehavior {
    void
fly();
}

//变化的 fly() 行为的实现类之一
public class FlyWithWings implements
FlyBehavior {
    public void fly() {
        System.out.println("I‘m
flying.");
    }
}

//变化的 fly() 行为的实现类之二
public class FlyNoWay
implements FlyBehavior {
    public void fly() {
       
System.out.println("I can‘t fly.");
   
}
}

-----------------------------------------------------------------

//变化的
quack() 行为定义形成的接口
public interface QuackBehavior {
    void
quack();
}

//变化的 quack() 行为实现类之一
public class Quack implements
QuackBehavior {
    public void quack() {
       
System.out.println("Quack");
    }
}

//变化的 quack()
行为实现类之二
public class Squeak implements QuackBehavior {
    public void
quack() {
        System.out.println("Squeak.");
    }
}

//变化的
quack() 行为实现类之三
public class MuteQuack implements QuackBehavior {
   
public void quack() {
        System.out.println("<< Slience
>>");
   
}
}

通过以上设计,fly()行为以及quack()行为已经和Duck.java没有什么关系,可以充分得到复用。而且我们很容易增加新的行为,既不影响现有的行为,也不影响Duck.java。但是,大家可能有个疑问,就是在面向对象中行为不是体现为方法吗?为什么现在被定义形成类(例如Squeak.java)?在OO中,类代表的"东西"一般是既有状态(实例变量)又有方法。只是在本例中碰巧"东西"是个行为。既使是行为,也有属性及方法,例如飞行行为,也需要一些属性记录飞行的状态,如飞行高度、速度等。

2)
整合变化的内容和不变的内容

Duck.java将
fly()以及quack()的行为委拖给行为类处理。

不变的内容:

public abstract class Duck
{

//将行为类声明为接口类型,降低对行为实现类型的依赖
    FlyBehavior flyBehavior;
   
QuackBehavior quackBehavior;

public void performFly() {
   
//不自行处理fly()行为,而是委拖给引用flyBehavior所指向的行为对象
        flyBehavior.fly();
   
}

public void performQuack() {
        quackBehavior.quack();
 
  }

public void swim() {
        System.out.println("All ducks
float, even decoys.");      
    }

public abstract void
display();
}

Duck.java不关心如何进行 fly()以及quack(),
这些细节交由具体的行为类完成。

public class MallardDuck extends Duck{
    public
MallardDuck() {
        flyBehavior=new FlyWithWings();
       
quackBehavior=new Quack();      
    }

public void display()
{
        System.out.println("Green head.");
   
}
}

测试类:

public class DuckTest {
    public static void
main(String[] args) {
        Duck duck=new MallardDuck();
       
duck.performFly();
        duck.performQuack();      
   
}
}

在Duck.java子类型MallardDuck.java的构造方法中,直接实例化行为类型,在编译的时侯便指定具体行为类型。当然,我们可以:

1)
我们可以通过工厂模式或其它模式进一步解藕(可参考后续模式讲解);
2) 或做到在运行时动态地改变行为。

3)
动态设定行为

在父类Duck.java中增加设定行为类型的setter方法,接受行为类型对象的参数传入。为了降藕,行为参数被声明为接口类型。这样,既便在运行时,也可以通过调用这二个方法以改变行为。

public
abstract class Duck {
    //在刚才Duck.java中加入以下二个方法。
    public void
setFlyBehavior(FlyBehavior flyBehavior) {
       
this.flyBehavior=flyBehavior;
    }

public void
setQuackBehavior(QuackBehavior quackBehavior) {
       
this.quackBehavior=quackBehavior;
   
}

//其它方法同,省略...
}

测试类:

public class DuckTest {
   
public static void main(String[] args) {
        Duck duck=new
MallardDuck();
        duck.performFly();
       
duck.performQuack();
        duck.setFlyBehavior(new FlyNoWay());
       
duck.performFly();
   
}
}

如果,我们要加上火箭助力的飞行行为,只需再新建FlyBehavior.java接口的实现类型。而子类型可通过调用setQuackBehavior(...)方法动态改变。至此,在Duck.java增加新的行为给我们代码所带来的困绕已不复存在。

该是总结的时侯了,让我们从代码的水中浮出来,做一只在水面上自由游动的鸭子吧:

3. 
解决方案

MallardDuck 继承  Duck抽象类;          -> 不变的内容
FlyWithWings 实现
FlyBehavior接口;     -> 变化的内容,行为或算法
在Duck.java提供setter方法以装配关系;    ->
动态设定行为

以上就是策略模式的实现三步曲。接下来,让我们透过步骤看本质:

1)
初始,我们通过继承实现行为的重用,导致了代码的维护问题。          -> 继承, is a
2)
接着,我们将行为剥离成单独的类型并声明为不变内容的实例变量并通过  -> 组合, has
a
setter方法以装配关系;

继承,可以实现静态代码的复用;组合,可以实现代码的弹性维护;使用组合代替继承,可以使代码更好地适应软件开发完后的需求变化。

策略模式的本质:少用继承,多用组合

时间: 2024-11-08 20:11:28

设计模式解读之一: 策略模式的相关文章

设计模式进阶(一) 策略模式

摘自<Design Paterns_Elements of Reusable Object-Oriented Software> 上一系列偏重于入门,从本篇开启进阶系列,着重于设计模式的适用情景. 回顾入门系列 设计模式入门(一)  策略模式 1  Intent Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary

设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕该功能.如查找.排序等,一种经常使用的方法是硬编码(Hard Coding)在一个类中,如须要提供多种查找算法,能够将这些算法写到一个类中,在该类中提供多个方法,每个方法相应一个详细的查找算法:当然也能够将这些查找算法封装在一个统一的方法中,通过if-else-或者case等条件推断语句来进行选择.

设计模式实现C++ --策略模式Strategy(对象行为型)

1.问题 出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机.每个策略都可以得到相同的结果,但是它们使用了不同的资源.选择策略的依据 是费用,时间,使用工具还有每种方式的方便程度. 2.解决方案 策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换.本模式使得算法可独立于使用它的客户而变化. 策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想. strategy模式类图: 3.应用场景 1. 

从“假如有以下几种价格10,20,50,请你代码实现将他们排序输出”看着设计模式中的策略模式

今天重温了一下策略模式,将自己的一些感悟与大家分享...本人只是技术渣渣,所理解的东西的难免会有很大的局限性甚至是错误,还请各位带着批判的眼光去看待....不喜请勿吐槽 定义:策略模式属于设计模式中的对象行为型模式,它将用到的算法单独抽象成一个单独的类.通常,我们在多个类完成同一件事情,仅仅完成的方式不同时,我们可以考虑使用这种设计模式. 举例:相信,很多人在看到"假如有以下几种价格10,20,50,请你代码实现将他们排序输出"这种题目的时候,很快就写出了以下代码,写之前还不忘了问一下

【转】设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查找.排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法:当然也可以将这些查找算法封装在一个统一的方法中,通过if-else-或者case等条件判断语句来进行选择.这

设计模式-行为型-策略模式

策略模式(Strategy): 策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理.策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类.用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”. 策略模式的角色: 1)环境类(Context):采用组合或聚合的方式维护一个对Strategy对象的引用. 2)抽象策略类(Strategy):定义所有支持的算法的公共接口.Context使用这个接口来调用某Concret

设计模式(一):策略模式

一.设计背景 现实生活中,我们要做一件事情或者完成某项工作,往往有很多种途径.比如我们出游,可以选择坐汽车,坐火车,土豪点的选择是坐飞机.还有我们现在线下的支付方式也有了很多种选择,以前在外面忘了带钱的话可能一瓶水都难以买到,现在只要我们手机在身上,可以用微信或者支付宝. 在软件设计层面,我们把各种支付方式叫做策略.如果不考虑设计模式的话,我们可能会在一个类中用if..else方式来选择支付方式,即 if(type == 1){ //微信支付 }else if(type == 2){ // 支付

[设计模式] javascript 之 策略模式

定义: 封装一系列的算法,使得他们之间可以相互替换,本模式使用算法独立于使用它的客户的变化. 说明:策略模式,是一种组织算法的模式,核心不在于算法,而在于组织一系列的算法,并且如何去使用它:策略模式作用在于,行为实现的不可预见,面对这样的一种变化,我们得思考如何使用程序好维跟扩展,并使得客户很好的使用算法的方式: 策略模式 使用要注意它 "变化" 的一面,策略模式就是来解决这个 变化 问题的. 比如商场买卖的价格或促销问题,如果不使用模式,就可能只是 把“所有”的情况用 if else

Java设计模式6:策略模式

策略模式 策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使得它们可以相互替换.策略模式使得算法可以在不影响到客户端的情况下发生变化. 策略模式的结构 策略模式是对算法的包装,是把使用算法的责任和算法本身分开.策略模式通常是把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类. 策略模式涉及到三个角色: 1.环境角色 持有一个策略Strategy的引用 2.抽象策略角色 这是一个抽象角色,通常由一个接口或抽象类实现,此角色给出所有具体策略类所需的接口 3.

head first 设计模式(-) 策略模式(鸭子)

目的:减少依赖 设计模式对应不同的需求,设计原则则代表永恒的灵魂,在实践中未必时刻遵守,但要时刻牢记. 1.依赖倒转原则(Dependence Inversion Principle) 2.接口隔离原则(Interface Segregation Principle) 3.里氏代换原则(Liskov Substitution Principle) 4.开闭原则(Open Close Principle) 5.迪米特法则(Demeter Principle) 6.合成复用原则(Composite