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 - 开闭原则:扩展开放,修改关闭
- l - 里氏替换原则:父类对象的引用换成子类对象时,调用的方法行为不能改变,即子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法
- i - 接口隔离原则:将臃肿庞大的接口分解为多个粒度小的接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性
- d - 依赖倒转原则
- d - 迪米特法制:只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)
事实上,设计模式就是对这六大原则的良好实践,只要我们在开发过程中严格遵循这六大原则,多思考,我们写出来的代码很大程度上会与设计模式中的某种模式不谋而合,这也是很多经验丰富,或源码看多的程序员就算没有接触过设计模式,但他们的代码就已经体现了设计模式的原因。
3 策略模式
3.1 意图
定义一系列算法,把它们一个个独立封装起来,并且使它们之间可以相互替换,该模式可以使得算法独立于它的使用者而变化。
3.2 适用性
- 一个类中定义了多个行为,每个行为通过多个条件语句来控制。将这些行为分别定义为一个策略类。
- 算法中的数据不能暴露给客户。使用策略模式可以避免暴露复杂的,与算法相关的数据结构。
- 在某种情况下,需要使用到一个算法的不同变体。比如系统原来只有微信支付一种方式的,现在要增加支付宝支付的方式;
- 许多相关的类仅仅只有行为有差异。“策略模式” 提供了一种从多个行为中选择一个行为来配置一个类的方法。
3.3 结构
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();
}
}