`
y806839048
  • 浏览: 1120944 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

策略模式

阅读更多
//策略模式的本质:少用继承,多用组合
//把基本的共有的特性放到一个基类中,其他的有变动的特性,抽象成接口,并对应应用实现,对于后面要用到某个特殊类可以,用基类+特殊接口类的一个组合。


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

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

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

分享到:
评论

相关推荐

    策略模式结合模板方法模式

    策略模式结合模板方法模式的设计思路 策略模式结合模板方法模式是策略模式的一种变形,目的是为了解决策略模式中的一些共性问题。在策略模式中,经常会出现这样一种情况,就是发现这一系列算法的实现上存在公共功能...

    详解SpringBoot结合策略模式实战套路

    SpringBoot结合策略模式实战套路 策略模式是一种常用的设计模式,它可以使我们的代码更加灵活、可维护和可扩展。在SpringBoot项目中,策略模式可以与依赖注入机制相结合,实现更加灵活的业务逻辑处理。在本文中,...

    设计模式之策略模式 鸭子问题

    设计模式之策略模式 鸭子问题 策略模式是一种经典的设计模式,通过鸭子问题,可以让学习者更好地了解设计模式的概念和实现。策略模式的主要思想是定义一系列的算法,并将每一个算法封装起来,使它们可以相互替换。...

    桥接模式和策略模式的区别,内含可运行代码和两者详细区别

    桥接模式和策略模式是软件设计模式中的两种重要模式,它们在实现上有着相似之处,但各自的应用场景和设计理念有所不同。下面将详细阐述这两种模式的特点、区别以及它们在实际编程中的应用。 首先,桥接模式(Bridge...

    策略模式在实际项目中的应用二

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式通过定义一系列的算法,并将每一个算法封装起来,使它们可以相互替换,让算法独立于使用它的客户而变化。这种模式通常用于处理多种...

    Spring下使用策略模式

    在Spring框架中,策略模式是一种常见的设计模式,它允许我们定义一组可互换的策略,这些策略可以在运行时根据需求动态选择。这篇文章将深入探讨如何在Spring中运用策略模式,并结合源码分析其工作原理。 策略模式的...

    策略模式的实现,通过反射

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式通常用于将算法封装到不同的类中,使得可以根据需要动态选择并应用这些算法。本示例将详细介绍如何通过两种方法实现策略模式:一种...

    抽象工厂模式+工厂方法模式+策略模式+类图实现手机加工厂

    本文将探讨三个重要的设计模式:抽象工厂模式、工厂方法模式以及策略模式,并结合一个实际的场景——手机加工厂,来具体阐述它们的应用。 首先,我们来看**抽象工厂模式**。这个模式主要用于创建相关或依赖对象的...

    55-Java设计模式之策略模式与状态模式1

    Java 设计模式之策略模式与状态模式 策略模式是 Java 中的一种设计模式,它主要用于解决系统与第三方接口进行数据交互的问题。当系统需要与多种格式的数据进行交互时,使用策略模式可以很好地解决这个问题。例如,...

    策略模式封装的几个加密解密算法源码

    在"策略模式封装的几个加密解密算法源码"中,我们主要关注的是如何使用策略模式来封装常见的加密解密算法,如BASE64和MD5。 1. **BASE64编码**:BASE64是一种用于将二进制数据编码为ASCII字符的编码方式,以便在...

    策略模式的简单例子

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在软件开发中,我们经常遇到需要根据不同条件或选择执行不同算法的情况。策略模式提供了一种将算法封装到独立可互换的策略对象中,使得算法的变化独立...

    设计模式之策略模式,商场收银,封装算法

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在软件开发中,我们经常遇到需要根据不同的条件或场景来执行不同算法的情况。策略模式就是为了解决这类问题而提出的,它将每种算法封装到具有共同接口...

    js策略模式和代理模式

    策略模式和代理模式是设计模式中的两种常见模式,它们在软件开发中扮演着重要的角色,尤其是在JavaScript中,这两种模式提供了更加灵活和可维护的代码结构。 策略模式(Strategy Pattern)是一种行为设计模式,它...

    策略模式 template模式

    策略模式(Template模式) 策略模式是设计模式中的一种 객체行为型模式,它定义了一系列算法,封装每一个算法,并使它们可以互相替换。策略模式使得算法可以独立于使用它的客户而变化。 概述 在软件开发中,经常...

    Java 设计模式 策略模式

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式主要通过定义一系列的算法,并将每一个算法封装起来,使它们可以互相替换,让算法独立于使用它的客户而变化。 首先,策略模式的...

    策略模式的示例代码和思想模式

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式通常涉及接口或抽象类的实现,允许程序在运行时选择并应用不同的算法或策略。这种模式的核心在于将算法封装到独立的可互换的策略中...

    Java策略模式+案例

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在Java中,策略模式允许我们定义一组算法或策略,并将每个策略封装为一个类,使得它们可以互换,而不会影响到客户端代码。这种模式的核心在于"策略",...

    策略模式代码实现

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在策略模式中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为模式。 策略模式定义了一系列的算法,并将每一个算法封装起来,使...

    设计模式——策略模式

    策略模式的设计与实现 策略模式是一种常用的设计模式,它定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。策略模式的主要优点是它可以使得算法的变化独立于使用算法...

    软件设计模式策略模式实例

    策略模式是一种行为设计模式,它使你能在运行时改变对象的行为。在软件工程中,当一个系统需要在不同时间执行不同的算法或者行为时,策略模式就显得尤为有用。这种模式将算法封装到独立的可相互替换的策略类中,使得...

Global site tag (gtag.js) - Google Analytics