「设计模式(一) - 策略模式」
一、从if-else说起
代码中if-else
的出现频率不必多说,几乎的逻辑实现都离不这个组合。但是带来了方便的同时,也带来了多重嵌套代码块。套用重构
的一句话,这些都是代码的坏味道。过多的条件判断必然增加了系统不稳定性,同时也给扩展带来了不便因素。当然优化的方式多种多样,策略模式Strategy
仅仅只是其中简单的一种。
二、策略模式 Strategy
行为模式的一种,定义了一系列平行的算法,将实现与责任相分离并加以封装,当然各个策略之间是可以相互转化的。由客户端决定使用何种策略,由于策略之间的独立,为系统提供了很好的扩展性。可以理解为相同行为不同实现的组合。
三、组成部分
抽象的策略(Strategy)类:公共的策略接口
strategy
,派生出不同的实现算法(行为是相同的),当然这个抽象的策略一般是接口,也有情况下是抽象类(具有公共的主体)。具体实现(Concrete Strategy):策略接口或者策略抽象类的具体实现类。
上下文(Context):分两种情况,一种是客户端本身,另一种情况则是持有策略的引用。
-
结构图:
四、简单的代码实现
1.设计一个数据加密的系统
数据加密的方式有很多种,AES、DES、RSA等等。加密的方式多种多样,但是在一个加密的系统中,
加密
这个行为是同样。而实现这个行为的方法是不同的。加密
在这里是策略的抽象。
- 定义
加密
策略顶层接口
/**
* Created by Sai
* on: 09/01/2022 01:08.
* Description:策略接口
*/
public interface EncryptionAlgorithm {
String encrypt(String message);
}
- 以AES的方式进行加密-策略的具体实现
/**
* Created by Sai
* on: 09/01/2022 01:08.
* Description:
*/
public class AES implements EncryptionAlgorithm {
@Override
public String encrypt(String message) {
System.out.println("Encrypting message using AES");
return "AES ---> " + " " + message;
}
}
- 以DES的方式进行加密
/**
* Created by Sai
* on: 09/01/2022 01:08.
* Description:
*/
public class DES implements EncryptionAlgorithm {
@Override
public String encrypt(String message) {
System.out.println("Encrypting message using DES");
return "DES --->" + " " + message;
}
}
- 以RSA的方式进行加密
/**
* Created by Sai
* on: 09/01/2022 01:12.
* Description:
*/
public class RSA implements EncryptionAlgorithm {
@Override
public String encrypt(String message) {
System.out.println("Encrypting message using RSA");
return "RSA ---> " + " " + message;
}
}
- 客户端持有类用类
/**
* Created by Sai
* on: 09/01/2022 01:15.
* Description:
*/
public class Client {
private final EncryptionAlgorithm encryptionAlgorithm;
public Client(EncryptionAlgorithm encryptor) {
this.encryptionAlgorithm = encryptor;
}
public void handleMessage(String message) {
var encryptedMessage = encryptionAlgorithm.encrypt(message);
System.out.println(encryptedMessage);
}
}
这里客户端Client
持有了策略对象,通过上下文Context
设置关系,具体选择哪种策略是由客户端来作出选择。另一种则是由上下文来决定何种策略,调用顺序上是由差别的,但是影响不大。当然也是根据具体的业务来合理选择。
- 测试用例
/**
* Created by Sai
* on: 09/01/2022 01:17.
* Description:
*/
public class Demo {
public static void show() {
//选择AES进行加密
var client = new Client(new AES());
client.handleMessage("send messages...");
}
public static void main(String[] args) {
show();
}
}
2.一些思考
虽然是个简单的例子,但是也能看出策略模式的一些优缺点,即使现在需要增加一个新的加密算法;那么同样只需要实现顶层策略
接口即可(同一行为的不同实现,面向接口的便利)
它是易扩展的,但是并不代表着可以无限的增长。在例子中也发现了一个问题,同一时刻只有一个策略被执行,也造成了一定的维护成本。其次,客户端必须了解所有的策略(或者上下文Context)
,具体选择哪种策略是需要客户端来主动选择的。回想之前的开闭原则
,这里其实已经暴露了具体的实现。当然瑕不掩瑜,策略模式是解决多重条件嵌套有效方式之一。
3.没有完美的设计模式
N种策略但同一时刻只有一种被用到了,多少有点浪费,以上述例子为例,其实可以考虑使用责任链模式替换。而且并不会暴露具体的内部实现,那是不是说责任链模式就比策略模式好呢?答案是否定的,考虑到加密方式的繁多性,责任链的链调用深度势必会很深。看,同样是有瑕疵的。没有完美的设计模式(不然也不会出现经典的23种)
。但是某种情况下,各种模式的配合可以趋于完美
。学习设计模式,个人觉得还是思想的学习吧。
五、开发中的实际问题
最近开发中就遇到了类似的问题,服务端WebSocket
下发通知,需要处理这个通知来控制打印机工作,根据Notify Type
的不同打印不同的信息文本。而目前已经实现的只有一种type
,考虑到type
的扩展性,想以策略形式抽象出来。
private void processPrint(PrintMessage printMessage) {
if (printMessage == null || CheckUtil.isEmpty(printMessage.getPrintType())) {
return;
}
IFetchDataDetail fetchDataDetail;
if (printMessage.isCondition()) {
fetchDataDetail = new PrintXXPrintRequest();
} else {
fetchDataDetail = new PrintNormalPrintRequest();
}
fetchDataDetail.onFetchDataToPrint(printMessage);
}
public interface IFetchDataDetail {
void onFetchDataToPrint(PrintMessage message);
}
这里的抽象出来的核心操作只有一个,尽管type是不同的,但是都是通过返回的id查询详情并且打印,那么统一的行为就是
IFetchDataDetail
,只有一个方法void onFetchDataToPrint(PrintMessage message)
至于打印何种信息,那就是策略内部的具体实现了。
但它是策略模式吗?其实不是的,与策略模式唯一的区别是没有上下文Context
,可以说是退化了的策略模式:退化成简单的面向接口编程,仅仅对多重判断做了整理优化。但接口隔离优点还是有体现的,表面上看没有上下文影响不大。可事实上,客户端的难度增加了,因此还是可以继续优化的。设计的唯一目的,不就是降低客户端的使用复杂度嘛。
六、应该什么时候用
- 仅仅只是行为实现上的区别-同一行为不同实现。
- 优化多重条件的嵌套问题。
- 具体业务场景具体分析,重要的还是抽象思想的理解。