`
kael____
  • 浏览: 19222 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

java设计模式--策略模式

阅读更多

当我们掌握了Java的语法,当我们了解了面向对象的封装、继承、多态等特性,当我们可以用Swing、Servlet、JSP技术构建桌面以及Web应用,不意味着我们可以写出面向对象的程序,不意味着我们可以很好的实现代码复用,弹性维护,不意味着我们可以实现在维护、扩展基础上的代码复用。一把刀,可以使你制敌于无形而于江湖扬名,也可以只是一把利刃而使你切菜平静。Java,就是这把刀,它的威力取决于你使用的方式。当我们陷入无尽无止重复代码的泥沼,当我们面临牵一发而动全身的维护恶梦, 你应该想起“设计模式”这个行动秘笈。面向对象的精义,看似平淡,其实要经过艰苦实践才能成功。而构造OO系统的隐含经验于是被前人搜集而成并冠以“设计模式”之名。我们应该在编码行动初始就携带以它。接下来,让我们步“四人组”先行者之后,用中国文字、用实际案例领略模式于我们代码焕然一新的改变:

 

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

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

2. 问题缘起

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

 

示例,一个鸭子类型:(示例来源于head first.设计模式 )

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) 整合变化的内容和不变的内容

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方法以装配关系;

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

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

分享到:
评论

相关推荐

    java设计模式---诙谐易懂版

    根据给定文件内容,以下是关于Java设计模式的知识点说明: 1. 策略模式(Strategy Pattern)是一种行为设计模式,允许在运行时选择算法的行为。策略模式的意图是定义一系列算法,将每个算法封装起来,并使它们可以...

    java设计模式-策略模式

    在这个“java设计模式-策略模式”的示例中,我们将深入探讨如何使用策略模式来实现一个简单的超市促销系统。 策略模式的核心思想是定义一系列的算法,并将每一个算法封装起来,使它们可以互相替换。这使得算法的...

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

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

    JAVA设计模式-chm版

    这个“JAVA设计模式-chm版”资源显然包含了关于Java设计模式的详细信息,便于理解和应用。设计模式是对常见问题的解决方案的标准化描述,它们在软件工程中起到了重要的作用,帮助开发者创建可维护、可扩展且易于理解...

    java设计模式----单例模式

    在Java编程语言中,设计模式是一种经过验证的解决常见软件设计问题的最佳实践。单例模式是其中最常用的一种,它的核心思想是确保一个类只有一个实例,并提供全局访问点。单例模式的应用场景包括:控制资源的访问、...

    Java设计模式-策略模式

    总结来说,Java设计模式中的策略模式是一种非常实用的设计模式,它允许我们在运行时选择和改变算法,提高了代码的可维护性和灵活性。通过观看厉风行老师的教程,你可以深入理解策略模式的原理,并将其运用到实际项目...

    JAVA设计模式--程序设计--反射--注解--泛型

    Java设计模式、程序设计、反射、注解和泛型是Java开发中的核心概念,它们各自在不同的场景下发挥着重要作用,构建出高效、可维护的软件系统。 首先,Java设计模式是面向对象编程中的一种最佳实践,是解决常见问题的...

    JAVA设计模式例程-策略模式

    在这个“JAVA设计模式例程-策略模式”的压缩包中,我们可以深入探讨策略模式的原理及其应用。 策略模式的核心思想是定义一系列算法,并将每一个算法封装起来,使它们可以相互替换。策略对象的使用让算法的变化独立...

    JAVA设计模式---100%推荐

    这篇名为"JAVA设计模式---100%推荐"的资源,可能是对Java设计模式的深入解析,旨在帮助开发者更好地理解和应用这些模式。 设计模式通常分为三类:创建型、结构型和行为型。创建型模式涉及对象的实例化过程,如单例...

    设计模式--策略模式java例子

    综上所述,策略模式在Java编程中是一个非常实用的设计模式,通过将算法封装到独立的策略类中,可以有效地管理和切换不同的行为,从而提升代码的灵活性和可维护性。在压缩包文件"StrategyPatterns例子"中,你可以找到...

    java设计模式的应用

    ### Java设计模式的应用 #### 一、引言 在当今快速发展的软件开发领域,Java作为一门功能强大且灵活的语言,不仅拥有丰富的API资源,还能与强大的数据库系统无缝对接。这使得许多开发人员能够以模块化的形式构建...

    java设计模式--工厂模式

    Java设计模式中的工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式。工厂模式的核心思想是将对象的创建过程封装起来,使得创建过程独立于使用过程,从而提高了代码的可扩展性和可维护性。在HeadFirst...

    JAVA设计模式-设计模式公司出品

    JAVA设计模式是编程领域中非常重要的概念,它为软件开发人员提供了在特定情境下解决常见问题的模板。设计模式公司出品的《JAVA设计模式》书籍,详细介绍了多种设计模式,适合不同水平的程序员学习和使用。 首先,...

    设计模式-Java语言中的应用

    通过阅读《设计模式-Java语言中的应用》,读者不仅可以掌握设计模式的基本概念和原理,还能学习到如何在实际项目中选择合适的设计模式,以及如何优雅地在Java代码中实现这些模式。这将有助于提升开发者的编程技巧,...

    JAVA设计模式-原则和23种设计模式归纳总结

    本资源主要介绍了JAVA设计模式的原则和23种设计模式的总结。设计模式是软件设计中的一种解决方案,能够使软件系统更加灵活、可维护和可扩展。本资源首先介绍了设计模式的六大原则,包括单一责任原则、开闭原则、里氏...

    java与模式-阎宏

    《Java与模式》是阎宏博士的一本经典著作,它深入浅出地介绍了如何在Java编程中应用设计模式。这本书不仅讲解了设计模式的基本概念,还涵盖了23种经典的GOF设计模式,并结合Java语言特性进行了详细的解释和实例演示...

Global site tag (gtag.js) - Google Analytics