设计模式-01策略模式

1 什么是设计模式

先看一段设计模式“总结之父“(为什么说是”总结之父呢“,因为设计模式不是被发明出来的,而是对开发经验的总结)们GOF在《设计模式 可复用面向对象软件的基础》一书中描述的一段话:
在这里插入图片描述
设计模式就是程序编码设计时的一些套路,这些套路都是经过前人千锤百炼总结出来的经验,由GoF总结出23种经典套路,即23种设计模式,通俗的说我们只要深入理解了这23种套路,在编码过程中套用上去,就能得到结构良好的程序。当然设计模式并不规定死是这23种,毕竟本质是对编码经验的总结,如果你愿意你也可以发明一个设计模式,只是是否实用,能否得到广泛的认可罢了。

注:GoF是设计模式的经典名著Design Patterns: Elements of Reusable Object-Oriented Software(中译本名为《设计模式——可复用面向对象软件的基础》)的四位作者,他们分为是:Elich Gamma、Richard Helm、Ralph Johnson、以及John Vlissides。这四个人常被称为Gang of Four, 即四人组,简称GoF。

2 设计模式的六大原则

  • s - 单一职责

  • o - 开闭原则:扩展开放,修改关闭

    1. 问:我们如何让设计的每个部分都遵循开闭原则?
      答:通常,你办不到。要让OO设计同时具备开放性和关闭性,又不修改现有的代码,需要花很多的时间和努力。一般来说,我们实在没有闲工夫把设计的每个部分都这么设计(而且,就算做得到,也可能只是一种浪费)。遵循开放-关闭原则,通常会引入新的抽象层次,增加代码的复杂度。你需要把注意力集中在设计中最有可能改变的地方,然后应用开放-闭合原则。
    2. 问:我怎么知道,哪些地方的改变是更重要的呢?
      答:这就涉及到设计系统的经验,和对你工作领域的了解。多看一些其他的例子可以帮你学习如何辨别设计中的变化区。
      以上问答引用自《Head First 设计模式》
  • l - 里氏替换原则:父类对象的引用换成子类对象时,调用的方法行为不能改变,即子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法

  • i - 接口隔离原则:将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性

  • d - 依赖倒转原则

  • d - 迪米特法制:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)

事实上,设计模式就是对这六大原则的良好实践,只要我们在开发过程中严格遵循这六大原则,多思考,我们写出来的代码很大程度上会与设计模式中的某种模式不谋而合,这也是很多经验丰富,或源码看多的程序员就算没有接触过设计模式,但他们的代码就已经体现了设计模式的原因。

3 策略模式

3.1 意图

定义一系列算法,把它们一个个独立封装起来,并且使它们之间可以相互替换,该模式可以使得算法独立于它的使用者而变化。

3.2 适用性

  • 一个类中定义了多个行为,每个行为通过多个条件语句来控制。将这些行为分别定义为一个策略类。
  • 算法中的数据不能暴露给客户。使用策略模式可以避免暴露复杂的,与算法相关的数据结构。
  • 在某种情况下,需要使用到一个算法的不同变体。比如系统原来只有微信支付一种方式的,现在要增加支付宝支付的方式;
  • 许多相关的类仅仅只有行为有差异。“策略模式” 提供了一种从多个行为中选择一个行为来配置一个类的方法。

3.3 结构

Context
-Strategy strategy
«interface»
Strategy
+operate()
StrategyA
+operate()
StrategyB
+operate()
StrategyC
+operate()

3.4 参与角色

  • Stratety:策略簇的接口,所有具体的算法都实现该接口
  • ConcreteStrategy:具体的策略,如StrategyA
  • Context:上下文,维护一个对Strategy的引用;用一个ConcreteStrategy对象来配置。可以定义一个接口来让Strategy访问它的数据。

3.5 角色之间的协作

  • Strategy 和 Context相互作用以实现选定的算法。当算法被调用时,Context可以将该算法所需的所有数据都传递给该Strategy。或者,Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context。
  • Context 将客户的请求转发给它的Strategy。客户通常创建并传递一个ConcreteStrategy给该Context,这样,客户仅与Context交互。通常有一系列的ConcreteStrategy类可供客户从中选择。

3.6 缺点

  • 用户必须了解不同的策略,就是一个用户要选择一个合适的策略就必须知道这些策略之间到有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同的行为和变体与客户相关时,才需要使用策略模式。
  • 增加了对象的数目

3.8 疑问

  • 为什么要有一个Context类呢,这是必须的吗?
    我认为这个Context不是必须的,或者说是模糊的,它也可以有用户来担任; 那么Context类的作用是什么呢?想象下在做项目过程中没有项目经理的角色,业务方(甲方)直接跟开发、测试进行交流,那是件多么糟糕的事情;当一个程序相当复杂的时候,Context是必要的,它可以存储一些必要的参数、数据结构,以简化用户对具体策略的调用,这也符合迪米特法则。

3.7 总结

总的来说就是迪米特法则 + 依赖倒转 + 单一职责 的具体应用,用接口定义统一的入口,具体类来实现不同的行为,将接口组合到主类来进行调用;

3.8 示例

当前市面上有很多种支付方式,如支付宝(ALIPAY)、微信(WEIXINPAY)、银联(UNIPAY)、、苹果支付(APPLEPAY)等,以后可能还会出现各种pay,商家在收钱的时候要提供各种二维码,相当繁琐,现在我要做一个聚合支付的功能,就是提供一个二维码,不同客户端的消费者只需要扫一个二维码就可以完成支付。
这里我们假设用不同的App扫我这个码时都会给我们后端传一个标识用于识别是哪个app扫的码,比如支付宝就是ALIPAY、微信就是WEIXINPAY。

步骤一:先定义一个抽象的父类

public interface IPay {  
    void pay();  
}

步骤二:定义不同的实现类

@Service("ALIPAY")  
public class AliaPay implements IPay {  
     @Override  
     public void pay() {  
        System.out.println("===发起支付宝支付===");  
     }  
}  
@Service("WEIXINPAY")  
public class AliaPay implements IPay {  
     @Override  
     public void pay() {  
        System.out.println("===发起微信支付===");  
     }  
}  
@Service("UNIPAY")  
public class AliaPay implements IPay {  
     @Override  
     public void pay() {  
        System.out.println("===发起银联支付===");  
     }  
}  

步骤三:提供统一调用服务

@Service
public class PayService{  
	@Autowired
	Map<String,IPay> payStrategys;
   	/** 
   	* payWay 是支付方式,如 ALIPAY
   	*/
    public void pay(String payWay) {  
         payStrategys.get(payWay).pay();
     }  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值