资讯专栏INFORMATION COLUMN

策略模式与模板方法模式的介绍与对比

myeveryheart / 1645人阅读

摘要:模式定义策略模式的定义策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。模板方法模式定义模板方法模式在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中。

最近在给公司的商城做第三方支付的对接,看了一下以前的微信支付,感觉结合了一下之前看的设计模式,想试试能不能在上面用上。一番研究后,感觉也是可以,就是可能有点牛刀小试。

模式定义

策略模式的定义
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

模板方法模式定义
模板方法模式在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

ROUND 1:多支付方式

1、 场景描述

 微信支付有预下单、退款、订单查询等等接口,看了下第三方支付,同样也是类似的三板斧。很容易,我们会想到以下这个类图:


  图中可以看到WxPay和SandPay都继承了Pay,他们有同样的行为:下单、退款、订单查询,但是,我们可以知道他们的实现肯定是不一样的,这样我们都必须覆盖父类的方法,来处理不一样支付的细节,这样的时候,代码就会显得冗余,每个子类都需要覆盖父类的实现,如何让代码更加统一呢?

2、 问题分析

这个时候,我们需要的是将行为独立出来,让其封装在特定的行为类里,这样,我们就能“指定”行为到支付的实例。比如说,我们想要产生一个新的第三方支付实例,我们可以动态的让其实现微信支付的下单操作(当然这是不合理)。

这时,我们用到了一个很重要的设计原则:

针对接口编程,而不是针对实现编程

我们用接口代表每个行为,比如说,OrderBehavior和RefundBehavior,行为的每个实现都讲实现其中一个接口。支付类不会实现下单和退款的接口,而是有其他类专门实现。我们称这种类叫“行为”类。和以前做法不一样的地方在于,以前的做法是:行为由超类或者子类继承某个接口,自行实现。这两种做法都依赖与“实现”,这样我们很容易被实现绑死,很难在后来改变行为(除非写更多的代码)。新的设计里,支付类将使用由接口所表示的行为,所以实际的“实现”并不会绑死在支付类中,这样,支付类就不用在了解行为实现的细节。

 来看一下新的类图:
          

以及,看看Pay抽象类中统一后的方法代码:

orderBehavior->orderPay();
    }
    
    public function refund()
    {
        return $this->refundBehavior->refund();
    }

}

然后在实现的时候,子类就能这样动态实现

orderBehavior = new WxOrder();
//实现了微信支付的下单
$pay->orderPay();

//第三方支付同理
$pay = new SandPay();
$pay->orderBehavior = new SandOrder();
$pay->orderPay();

可能这时候有人会问,我明明可以直接就在具体类里实现这个方法,也不用多写这么多类与接口,正常来说,支付也不会改变其实现方式。
是的,一般来说,支付是不会修改的,但是如果突然说现在不用第三方支付,全部都用微信支付,那么我们代码的修改可能会很多了,但是用这种实现我们只需要改动一个很小地方:

orderBehavior = new WxOrder();
//我们由于不用管实际的细节,也不用做太多的代码改动,而且由微信支付本来的稳定实现,我们也能很放心的说,代码不会出现bug
$pay->orderPay();

这一种实现方式,也符合了一个设计原则:

多用组合,少用继承

这里支付对下单甚至退款的操作,实际的实现都不是通过继承得到的,而是通过将其他类的结合,不仅可以将算法族封装成类,更可以“在运行时动态改变行为”,只要行为对象符合正确的接口标准即可。

这个情况,你觉得是用了哪种设计模式?


ROUND 2:同样的实现结构

1、 场景描述

  在处理完多种支付的场景后,我们在开始加入了第三方支付的代码,在写的过程发现,下单操作和微信支付类似,我们需要在请求
之前,组装请求必要参数、签名,得到返回结果后,对数据验签,然后进行自己的业务逻辑,最后返回一个应答。
  同理,退款操作也是一样:组装请求必要参数、签名等等,和下单操作几乎处理逻辑的顺序或者说结构很相似,如果我们继续这么
编写,会发现很多重复代码,这个时候要怎么处理呢?

2、 问题分析

  在解决问题之前,先说一下一个重要的设计原则:
    
找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
    
  通俗来说,就是要善于发现现实中的变与不变,抽离不变的地方,使其能复用,然后让变化的部分自行解决处理。
  在这个场景中,很特殊的,我们发现不管是微信的下单、退款,还是第三方的下单、退款,他们业务的逻辑几乎是一个流水线上出
来的,就是他们不变的地方,而变化的是什么呢?很明显就是具体的实现业务不同,这部分应该由业务自己实现。
  这样,我们将该描述用类图表示如下:

而代码具体如下:

generateRequestData();
        $this->generateSign();
        $this->request();
        $this->verifiedSign();
        $this->handleResponse();
        $this->returnMsg();
    }

    abstract protected function generateRequestData();
    
    abstract protected function generateSign();
    
    abstract protected function request();
    
    abstract protected function handleResponse();
    
    abstract protected function verifiedSign();
    
    abstract protected function returnMsg();
}

class WxOrder extends PayAction{

    public function orderPay()
    {
        $this->doAction();
    }
}

只针对这一系列操作,我们可以写成这个样子,这样所有的行为都一样了,只需要实现自身不一样的地方即可。

ROUND 3:结合的效果

1、如果把第一种情况的结构和第二种情况的结构,结合起来,最后是什么样子呢,我们可以看一下:

这样,整个结构就很清晰了,客户端调用实现了行为的下单或者退款类,不需要知道具体的细节,并且可以动态的改变行为的对象;而行为的操作的重复部分又被抽出来,只需要各自实现变化的部分,不变的部分都由抽象类PayAction先定义好,再由不一样的支付类实现公共的部分:签名与验签,甚至最后固定的返回正确和错误时的格式都是同一个支付里,相同的部分;最后不一样的只有不同请求时候,不一样的请求参数,以及获得返回参数后的不一样的处理了。
类和接口的确比一开始的设计要多了不少,但是结构非常清晰,而且修改的时候,可以很有针对性的修改,对于客户端来说,调用是透明的,对于提供服务的我们来说,不管是新增一个支付方式,还是多了一个支付操作,我们都可以很好的增加代码,不用修改现有代码,而这个也是一个经常说到的设计原则:

开闭原则:对修改关闭,对扩展开放

ROUND 4:解答与对比

第一和第二个场景,它们分别对应的设计模式是什么?

第一种设计模式是:策略模式,而第二种是:模板方法模式

第一种我们可以清楚的看到,我们对于如何运行一个行为的时候,我们是将其封装在一个类中处理,并且,它们可以相互替换。算法的改变由客户来决定,可以动态的改变。
第二种我们可以看到的是,整个算法的结构已经被定义好,跟着预定好的模板来编写我们的算法,就可以实现相似的功能有条不紊的编写下去,不会出现多余的部分,并且可以专心的处理自身特别的业务逻辑。
所以,以后如果代码的相关结构很相似,可以选用模板方法模式来编写;如果业务中的某些行为可以被抽象并且有需要动态改变的时候,可以考虑策略模式来编写。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/29352.html

相关文章

  • 大前端2018现在上车还还得及么

    摘要:面向对象三大特征继承性多态性封装性接口。第五阶段封装一个属于自己的框架框架封装基础事件流冒泡捕获事件对象事件框架选择框架。核心模块和对象全局对象,,,事件驱动,事件发射器加密解密,路径操作,序列化和反序列化文件流操作服务端与客户端。 第一阶段: HTML+CSS:HTML进阶、CSS进阶、div+css布局、HTML+css整站开发、 JavaScript基础:Js基础教程、js内置对...

    stormgens 评论0 收藏0
  • 大前端2018现在上车还还得及么

    摘要:面向对象三大特征继承性多态性封装性接口。第五阶段封装一个属于自己的框架框架封装基础事件流冒泡捕获事件对象事件框架选择框架。核心模块和对象全局对象,,,事件驱动,事件发射器加密解密,路径操作,序列化和反序列化文件流操作服务端与客户端。 第一阶段: HTML+CSS:HTML进阶、CSS进阶、div+css布局、HTML+css整站开发、 JavaScript基础:Js基础教程、js内置对...

    mylxsw 评论0 收藏0
  • 一文理清21种设计模式:用实例分析和对比

    摘要:设计模式无论是对于最底层的的编码实现还是较高层的架构设计都有着重要的指导作用。所谓光说不练假把式,今天我就把项目中常见的应用场景涉及到的主要设计模式及其相关设计模式总结一下,用实例分析和对比的方式在一片文章中就把最常见的种设计模式梳理清楚。 设计模式无论是对于最底层的的编码实现还是较高层的架构设计都有着重要的指导作用。所谓光说不练假把式,今天我就把项目中常见的应用场景涉及到的主要设计模...

    PrototypeZ 评论0 收藏0
  • 为Java程序员金三银四精心挑选300余道Java面试题答案

    摘要:为程序员金三银四精心挑选的余道面试题与答案,欢迎大家向我推荐你在面试过程中遇到的问题我会把大家推荐的问题添加到下面的常用面试题清单中供大家参考。 为Java程序员金三银四精心挑选的300余道Java面试题与答案,欢迎大家向我推荐你在面试过程中遇到的问题,我会把大家推荐的问题添加到下面的常用面试题清单中供大家参考。 前两天写的以下博客,大家比较认可,热度不错,希望可以帮到准备或者正在参加...

    tomorrowwu 评论0 收藏0

发表评论

0条评论

myeveryheart

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<