【设计模式专题】策略模式之多场景下的代码解耦"神器"
策略模式的定义
什么是策略模式?它的原理实现是怎么样的?
定义一系列算法,封装每个算法,并使他们可以互换,不同的策略可以让算法独立于使用它们的客户而变化。-- 《设计模式之美》
感觉有点抽象?那就来看一张结构图吧:
- Strategy(抽象策略) :抽象策略类,并且定义策略执行入口。Strategy角色负责决定实现策略所必需的接口(API)。在示例程序中,由strategy接口扮演此角色。 特定策略的抽象。
- ConcreteStrategy(具体策略) :实现抽象策略,实现接口方法。ConcreteStrategy角色负责实现Strategy角色的接口(API),即负责实现具体的策略(战略、方向、方法和算法)。
- Context(上下文) :运行特定的策略的类。负责使用Strategy角色。Context角色保存了ConcreteStrategy角色的实例,并使用ConcreteStrategy角色去实现需求(总之,还是要调用Strategy角色的接口)。可以认为由Context处理了跳转的功能。
这么看结构其实还是不复杂的,而且跟状态模式类似。
在《Java设计模式》第56页,辛格说:“行为模式的一个特定情况,是我们需要改变解决一个问题与另一个问题的方式。”但正如开闭原则所说,改变是不好的,而扩展是好的。因此,我们可以将两块代码封装在一个类中,而不是用一部分代码替换另一部分代码。然后可以创建代码所需要依赖累的抽象。
策略模式的优点
- 策略模式提供了对 “开闭原则” 的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
- 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。
- 策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式而是通过继承,这样算法的使用就 和算法本身混在一起,不符合 “单一职责原则”,而且使用继承无法实现算法或行为在程序运行时的动态切 换。
- 使用策略模式可以避免多重条件选择语句。多重条件选择语句是硬编码,不易维护。
- 策略模式提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。
策略模式的缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
- 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
- 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。
针对第一个客户端需要缓存所有策略类(策略枚举和策略Map缓存) 的缺点,可以通过工厂模式来进行策略类的创建及选择。针对第二个会策略类数量多的缺点,可以通过使用享元模式在一定程度上减少对象的数量。
适用场景
在真实的业务场景中策略模式还是有很多应用的,比如短信发送、优惠券发放、商品满减、会员充值等。
在社交电商中分享商品是一个很重要的环节,假设现在要我们实现一个分享图片功能,比如当前有 单商品、多商品、下单、会场、邀请、小程序链接等等多种分享场景。
下面是适用场景的提炼与总结:
- 一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,根据 “里氏代换原则” 和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需要维持一个数据类型是抽象算法类的对象。
- 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。
- 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。
代码重构实践
在代码逻辑判断的初始编码阶段,我们可能会采用If-else语句来对逻辑进行快速判断,于是代码结构如下所示:
If-else语句
// 测试业务逻辑
if (shareType.equals(ShareType.SINGLE.getCode())) {
SingleItemShare singleItemShare = new SingleItemShare();
singleItemShare.algorithm("单商品");
} else if (shareType.equals(ShareType.MULTI.getCode())) {
MultiItemShare multiItemShare = new MultiItemShare();
multiItemShare.algorithm("多商品");
} else if (shareType.equals(ShareType.ORDER.getCode())) {
OrderItemShare orderItemShare = new OrderItemShare();
orderItemShare.algorithm("下单");
} else {
throw new Exception("未知分享类型");
}
在代码的情况多了之后,可以使用Switch语句对If-else进行优化,如下所示:
Switch语句
JSONObject message = fuseUploadInfo.getJSONObject("data");
switch (messageType) {
case MessageType.FUSE_TARGET:
handleFuseTarget(message);
break;
case MessageType.DEVICE_STATE:
handleDeviceMessage(message);
break;
case MessageType.TRACK:
handleTrackMessage(message);
break;
case MessageType.DEVICE_OPERATION_RESPONSE:
// 处理设备响应信息
webSocketService.sendMsg(new DeviceOperationResponseMessageTo(message));
break;
case MessageType.MESSAGE:
// 处理设备实时报文信息
log.info("设备实时报文:"+fuseUploadInfo);
handleDeviceRealTimeMessage(message);
break;
case MessageType.MESSAGE_LENGTH:
// 处理设备报文字节数
log.info("设备报文字节数:"+fuseUploadInfo);
deviceService.setDeviceMessageBytes(message.getString("deviceId"), message);
break;
default:
break;
}
但是,随着每种情况中的代码逻辑的增多,会导致类代码的臃肿。
这时候,我们需要对类进行拆分,以提供代码的整洁性和可维护性;于是,我们可以针对判断条件进行代码逻辑拆分。
因此,这时候策略模式就派得上用处了。
原代码
public class SingleItemShare {
// 单商品
public void algorithm(String param) {
System.out.println("当前分享图片是" + param);
}
}
public class MultiItemShare {
// 多商品
public void algorithm(String param) {
System.out.println("当前分享图片是" + param);
}
}
public class OrderItemShare {
// 下单
public void algorithm(String param) {
System.out.println("当前分享图片是" + param);
}
}
public class ShareFactory {
public static void main(String[] args) throws Exception {
Integer shareType = 1;
// 测试业务逻辑
if (shareType.equals(ShareType.SINGLE.getCode())) {
SingleItemShare singleItemShare = new SingleItemShare();
singleItemShare.algorithm("单商品");
} else if (shareType.equals(ShareType.MULTI.getCode())) {
MultiItemShare multiItemShare = new MultiItemShare();
multiItemShare.algorithm("多商品");
} else if (shareType.equals(ShareType.ORDER.getCode())) {
OrderItemShare orderItemShare = new OrderItemShare();
orderItemShare.algorithm("下单");
} else {
throw new Exception("未知分享类型");
}
// .....省略更多分享场景
}
enum ShareType {
SINGLE(1, "单商品"),
MULTI(2, "多商品"),
ORDER(3, "下单");
/**
* 场景对应的编码
*/
private Integer code;
/**
* 业务场景描述
*/
private String desc;
ShareType(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public Integer getCode() {
return code;
}
// 省略 get set 方法
}
}
接下来就看看如何用策略模式进行重构:
重构后的代码
public interface ShareStrategy {
// 定义分享策略执行方法
void shareAlgorithm(String param);
}
public class OrderItemShare implements ShareStrategy {
@Override
public void shareAlgorithm(String param) {
System.out.println("当前分享图片是" + param);
}
}
// 省略 MultiItemShare以及SingleItemShare策略
// 分享工厂
public class ShareFactory {
// 定义策略枚举
enum ShareType {
SINGLE("single", "单商品"),
MULTI("multi", "多商品"),
ORDER("order", "下单");
// 场景对应的编码
private String code;
// 业务场景描述
private String desc;
ShareType(String code, String desc) {
this.code = code;
this.desc = desc;
}
public String getCode() {
return code;
}
// 省略 get set 方法
}
// 定义策略map缓存
private static final Map<String, ShareStrategy> shareStrategies = new HashMap<>();
static {
shareStrategies.put("order", new OrderItemShare());
shareStrategies.put("single", new SingleItemShare());
shareStrategies.put("multi", new MultiItemShare());
}
// 获取指定策略
public static ShareStrategy getShareStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
return shareStrategies.get(type);
}
public static void main(String[] args) {
// 测试demo
String shareType = "order";
ShareStrategy shareStrategy = ShareFactory.getShareStrategy(shareType);
shareStrategy.shareAlgorithm("order");
// 输出结果:当前分享图片是order
}
}
这里策略模式就已经改造完了。在client请求端,根本看不到那么多的if-else判断,只需要传入对应的策略类型(type)即可,这里我们维护了一个策略缓存map,在直接调用的ShareFactory获取策略的时候就直接是从换种获取策略类对象。
这里达到了根据应用场景进行解偶的目的,同时也避免了长串的if-else判断。
注意:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
总结
策略模式的核心思想是在一个计算方法中把容易变化的算法抽出来作为"策略"参数(type)传进去,从而使得新增策略不必修改原有逻辑。
参考
下面是关于策略模式理论和概念的参考来源:
下面是混合模式的相关参考:
欢迎点赞,谢谢各位大佬了ヾ(◍°∇°◍)ノ゙
转载自:https://juejin.cn/post/7156029977362694180