软件设计之模板方法与策略模式


模板方法与策略模式

策略模式是对算法的封装,把一系列的算法分别封装到对应的类中,并且这些类实现相同的接口,相互之间可以替换。在前面说过的行为类模式中,有一种模式也是关注对算法的封装——模版方法模式,对照类图可以看到,策略模式与模版方法模式的区别仅仅是多了一个单独的封装类Context,它与模版方法模式的区别在于:在模版方法模式中,调用算法的主体在抽象的父类中,而在策略模式中,调用算法的主体则是封装到了封装类Context中,抽象策略Strategy一般是一个接口,目的只是为了定义规范,里面一般不包含逻辑。其实,这只是通用实现,而在实际编程中,因为各个具体策略实现类之间难免存在一些相同的逻辑,为了避免重复的代码,我们常常使用抽象类来担任Strategy的角色,在里面封装公共的代码,因此,在很多应用的场景中,在策略模式中一般会看到模版方法模式的影子。

原文链接:https://blog.csdn.net/zhengzhb/java/article/details/7609670

 

策略模式的结构

 

封装类:也叫上下文,对策略进行二次封装,目的是避免高层模块对策略的直接调用。

抽象策略:通常情况下为一个接口,当各个实现类中存在着重复的逻辑时,则使用抽象类来封装这部分公共的代码,此时,策略模式看上去更像是模版方法模式。

具体策略:具体策略角色通常由一组封装了算法的类来担任,这些类之间可以根据需要自由替换。

策略模式代码实现

 

interface IStrategy {

    public void doSomething();

}

class ConcreteStrategy1 implements IStrategy {

    public void doSomething() {

       System.out.println("具体策略1");

    }

}

class ConcreteStrategy2 implements IStrategy {

    public void doSomething() {

       System.out.println("具体策略2");

    }

}

class Context {

    private IStrategy strategy;

   

    public Context(IStrategy strategy){

       this.strategy = strategy;

    }

   

    public void execute(){

       strategy.doSomething();

    }

}

 

public class Client {

    public static void main(String[] args){

       Context context;

       System.out.println("-----执行策略1-----");

       context = new Context(new ConcreteStrategy1());

       context.execute();

 

       System.out.println("-----执行策略2-----");

       context = new Context(new ConcreteStrategy2());

       context.execute();

    }

}

 

 

策略模式的优缺点

 

策略模式的主要优点有:

 

l  策略类之间可以自由切换,由于策略类实现自同一个抽象,所以他们之间可以自由切换。

l  易于扩展,增加一个新的策略对策略模式来说非常容易,基本上可以在不改变原有代码的基础上进行扩展。

l  避免使用多重条件,如果不使用策略模式,对于所有的算法,必须使用条件语句进行连接,通过条件判断来决定使用哪一种算法,在上一篇文章中我们已经提到,使用多重条件判断是非常不容易维护的。

策略模式的缺点主要有两个:

 

l  维护各个策略类会给开发带来额外开销,可能大家在这方面都有经验:一般来说,策略类的数量超过5个,就比较令人头疼了。

l  必须对客户端(调用者)暴露所有的策略类,因为使用哪种策略是由客户端来决定的,因此,客户端应该知道有什么策略,并且了解各种策略之间的区别,否则,后果很严重。例如,有一个排序算法的策略模式,提供了快速排序、冒泡排序、选择排序这三种算法,客户端在使用这些算法之前,是不是先要明白这三种算法的适用情况?再比如,客户端要使用一个容器,有链表实现的,也有数组实现的,客户端是不是也要明白链表和数组有什么区别?就这一点来说是有悖于迪米特法则的。

 

 

适用场景

 

  做面向对象设计的,对策略模式一定很熟悉,因为它实质上就是面向对象中的继承和多态,在看完策略模式的通用代码后,我想,即使之前从来没有听说过策略模式,在开发过程中也一定使用过它吧?至少在在以下两种情况下,大家可以考虑使用策略模式,

 

几个类的主要逻辑相同,只在部分逻辑的算法和行为上稍有区别的情况。

有几种相似的行为,或者说算法,客户端需要动态地决定使用哪一种,那么可以使用策略模式,将这些算法封装起来供客户端调用。

  策略模式是一种简单常用的模式,我们在进行开发的时候,会经常有意无意地使用它,一般来说,策略模式不会单独使用,跟模版方法模式、工厂模式等混合使用的情况比较多。

 

模板方法模式 https://blog.csdn.net/hguisu/article/details/7564039


1.概述

 

在面向对象开发过程中,通常我们会遇到这样的一个问题:我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序。但是某些步骤的具体实现是未知的,或者说某些步骤的实现与具体的环境相关。

例子1:银行业务办理流程

在银行办理业务时,一般都包含几个基本固定步骤:

取号排队->办理具体业务->对银行工作人员进行评分。

取号取号排队和对银行工作人员进行评分业务逻辑是一样的。但是办理具体业务是个不相同的,具体业务可能取款、存款或者转账。

 

2.问题

 

如何保证架构逻辑的正常执行,而不被子类破坏 ?

 

3.解决方案

 

模板方法:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 T模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。(Template Method Pattern:Definethe skeleton of an algorithm in an operation,deferring some steps tosubclasses.Template Methodletssubclasses redefine certain steps of an algorithmwithoutchanging the algorithm's structure. )

 

1)模板方法模式是基于继承的代码复用基本技术,模板方法模式的结构和用法也是面向对象设计的核心之一。在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。

2)在模板方法模式中,我们需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式的用意。模板方法模式体现了面向对象的诸多重要思想,是一种使用频率较高的模式。

 

4.适用性

 

模板方法应用于下列情况:

• 1) 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。

• 2)各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。首先识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。

• 3)控制子类扩展。模板方法只在特定点调用“ hook”操作 ,这样就只允许在这些点进行扩展。

 

5.结构

 

 

 

6.模式的组成

 

抽象类(AbstractClass): 定义抽象的原语操作(primitive operation) ,具体的子类将重定义它们以实现一个算法, 实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义

具体子类 (ConcreteClass):  实现原语操作以完成算法中与特定子类相关的步骤。

 

7.效果

 

模板方法模式的优点:

1)模板方法模式在一个类中形式化地定义算法,而由它的子类实现细节的处理。

2)模板方法是一种代码复用的基本技术。它们在类库中尤为重要,它们提取了类库中的公共行为。

3)模板方法模式导致一种反向的控制结构,这种结构有时被称为“好莱坞法则” ,即“别找我们,,我们找你”通过一个父类调用其子类的操作(而不是相反的子类调用父类),通过对子类的扩展增加新的行为,符合“开闭原则”

模板方法模式的缺点:

每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,但是更加符合“单一职责原则”,使得类的内聚性得以提高。

 

8.实现

 

我们使用银行业务实现,添加了hook方法,客户自己觉得评价服务。

 

<?php

/**

 * 模板方法模式:

 *

 * @author guisu

 */

 

/**

 * 抽象类

 */

abstract class AbstractBank

{

    private $_number ;

    /**

     *模板方法

     * 因为子类不能覆写一个被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。

     *

     */

    public final   function templateMethodProcess() 

    {

        $this->takeNumber();

        $this->transact();

        if($this->isEvaluateHook()) {

           $this->evaluateHook();

        }

    }

    /**

     * 基本方法—具体方法

     * 取号

     *

     */

    private  function takeNumber()   

    {

        return ++$this->_number;

    }

    /**

     *  //基本方法—抽象方法

     *

     */

    protected  abstract function transact();  

    /**

     * 基本方法—钩子方法

     *

     */

     protected function evaluateHook() {

       echo ' evaluateHook<br/>';

     }

    /**

     * 基本方法—钩子方法

     */

    protected function isEvaluateHook() {

        return true;

    }

 

}

 

/**

 * 具体子类:存款

 */

class ConcreteDeposit extends AbstractBank

{

    public function transact() {

       //实现代码

       echo 'Deposit', '<br>';

    }

}

 

/**

 * 具体子类:取款

 */

class ConcreteWithdraw extends AbstractBank

{

   public function transact() {

       //实现代码

       echo 'Withdraw', '<br>';

    }

}

 

/**

 * 具体子类:转账

 */

class ConcreteTrancfer extends AbstractBank

{

    public function transact() {

       //实现代码

       echo 'Trancfer', '<br>';

    }

}

$c = new  ConcreteTrancfer();

$c->templateMethodProcess();

 

 

9.与其他相关模式

 

1)策略模式:模板方法使用继承来改变算法的一部分。 Strategy使用委托来改变整个算法。模板方法模式与策略模式的作用十分类似,有时可以用策略模式替代模板方法模式。模板方法模式通过继承来实现代码复用,而策略模式使用委托,把不确定的行为集中到一个接口中,并在主类委托这个接口。委托比继承具有更大的灵活性。

 

10.模式的扩展

 

1)模板方法模式与控制反转(好莱坞原则)在模板方法模式中,子类不显式调用父类的方法,而是通过覆盖父类的方法来实现某些具体的业务逻辑,父类控制对子类的调用,这种机制被称为好莱坞原则(Hollywood Principle),好莱坞原则的定义为:“不要给我们打电话,我们会给你打电话(Don‘t call us, we’ll call you)”。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。模板方法模式充分的体现了“好莱坞”原则。由父类完全控制着子类的逻辑,子类不需要调用父类,而通过父类来调用子类,子类可以实现父类的可变部份,却继承父类的逻辑,不能改变业务逻辑。

 

2)模板方法模式符合开闭原则

 

 

模板方法模式意图是由抽象父类控制顶级逻辑,并把基本操作的实现推迟到子类去实现,这是通过继承的手段来达到对象的复用,同时也遵守了开闭原则。

 

父类通过顶级逻辑,它通过定义并提供一个具体方法来实现,我们也称之为模板方法。通常这个模板方法才是外部对象最关心的方法。在上面的银行业务处理例子中,templateMethodProcess这个方法才是外部对象最关心的方法。所以它必须是public的,才能被外部对象所调用。

 

子类需要继承父类去扩展父类的基本方法,但是它也可以覆写父类的方法。如果子类去覆写了父类的模板方法,从而改变了父类控制的顶级逻辑,这违反了“开闭原则”。我们在使用模板方法模式时,应该总是保证子类有正确的逻辑。所以模板方法应该定义为final的。所以AbstractClass类的模板方法templateMethodProcess方法应该定义为final。

 

模板方法模式中,抽象类的模板方法应该声明为final的。因为子类不能覆写一个被定义为final的方法。从而保证了子类的逻辑永远由父类所控制。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM