原文链接https://www.jylt.cc/#/detail?activityIndex=2&id=8d1912358fa1c1d8db1b44e2d1042b70AIBOT 你想 我来做AIBOThttps://chat.jylt.top/
定义
策略模式(Strategy Pattern:Define a family of algorithms,encapsulate each one,and make them interchangeable.)中文解释为:定义一组算法,然后将这些算法封装起来,以便它们之间可以互换,属于一种对象行为型模式。总的来说策略模式是一种比较简单的模式,听起来可能有点费劲,其实就是定义一组通用算法的上层接口,各个算法实现类实现该算法接口,封装模块使用类似于 Context 的概念,Context 暴漏一组接口,Context 内部接口委托到抽象算法层。
是不是每个字都能看懂,拼到一块不知所以然了。最初看到这句话时也不知道啥意思,下面有一个类图供大家看下:
组成角色很简单,对于新手来说,实现起来可能会毫无思路。其实主要使用的就是多态的特性。
实现
了解了基本概念之后,可能对策略模式仅仅是一个了解的状态,动手实操可能还没有什么思路。下面以我的AI项目AIBOT中图片生成功能来说一下策略模式是如何落地的。
先看一下具体的功能点:
目前AI作图只有两个功能,文生图和人像重绘,如何实现这两个功能呢?
普通实现方式
最简单高效的方法是平铺直叙的写代码,通过 if-else 来区分具体是哪个功能,例如:
@PostMapping("/genImg")
public ResponseKit<Object> genImg(int type) {
if (type == 1) {
// 文生图逻辑
} else if (type == 2) {
// 人像重绘逻辑
} else if (type == ...) {
// 其他图像生成逻辑
}
}
优点
- 代码实现方便,不需要做过多设计
- 代码条理比较清晰,自上而下很容易找出来哪块逻辑实现的是什么功能
缺点
- 会增加类的复杂性,这些逻辑虽都是图片生成,但具体实现逻辑不是同一类,都放到同一个类中,会使这个类变得大而全,与 职责单一 的设计规范相冲突
- 如果日后需要增加其他图像生成逻辑,需要频繁的修改这个类,而这个类中又有太多其他生成规则的逻辑,修改、新增逻辑可能会对其他已有逻辑造成意料之外的影响,为了避免这种影响可能引入的错误,我们就要对受影响的逻辑都要进行回归测试。这样无形间增加了测试的工作量,与 开闭原则 设计规范相冲突
策略模式实现
依据上面的类图,我们先一步步通过策略模式来实现功能,然后来看一下策略模式有什么优缺点
首先我们需要先定义一个通用接口:
public interface GenImgStrategy {
ResponseKit<Object> genImg();
}
接口很简单,就一个抽象方法:生成图片;然后我们定义一下 文生图 和 人像重绘 的具体实现逻辑。这两个逻辑都统一实现 GenImgStrategy
接口:
public class TextToImgStrategyImpl implemets GenImgStrategy {
@Override
public ResponseKit<Object> gen() {
// 文生图具体实现逻辑
}
}
public class GenImgStyleStrategyImpl implemets GenImgStrategy {
@Override
public ResponseKit<Object> gen() {
// 人像重绘具体实现逻辑
}
}
定义好了接口和实现类之后,我们如何通过 API 接口中的 type 参数来确定需要执行 文生图 的逻辑还是执行 人像重绘 的逻辑呢?这时候我们需要一个辅助类:
public class GenImgStrategyContext {
private static final Map<Integer, GenImgStrategy> map = new HashMap<>();
static {
// 初始化
map.put(1, new TextToImgStrategyImpl());
map.put(2, new GenImgStyleStrategyImpl());
map.put(..., ...);
}
// type=1:执行文生图逻辑;type=2:执行人像重绘逻辑
public ResponseKit<Object> doGen(Integer type) {
GenImgStrategy genImgStrategy = map.get(type);
if (genImgStrategy == null) {
log.error("类型异常,type={}", type);
return ResponseKit.error("参数异常");
}
return genImgStrategy.gen();
}
}
这里使用多态的特性,在执行 doGen
方法的时候,通过 type 值来获取不同策略的具体实现类,最后执行 gen
方法的时候,其实执行的是具体实现类中的方法
API 接口只需要改为以下形式,就可以轻松调用不同的图像生成逻辑了:
@PostMapping("/genImg")
public ResponseKit<Object> genImg(int type) {
GenImgStrategyContext context = new GenImgStrategyContext();
return context.doGen(type);
}
这样改造之后,我们就通过 策略模式 来实现了图片生成的两种实现逻辑,后续如果有新的图片生成逻辑,只需要再增加一个实现类,在 context 类的 map 中再增加一种类型即可,对现有的两种实现逻辑实现了零侵入,做功能验证的时候只需要验证新增的逻辑即可,无需担心对现有功能的其他影响。
综合以上,我们看一下策略模式的优缺点:
优点
- 极大的提高了程序的扩展性
- 使程序更加的 高内聚,低耦合
- 提高了后续功能扩展的效率
缺点
- 增加了程序的复杂性
- 如果策略较多,会有很多的策略实现类
看到这里之后,有些小伙伴可能还是懵懵懂懂的状态,毕竟纸上得来终觉浅,需要动手实操,最好是在自己的项目中实践一下,立即会有柳暗花明又一村的感觉。
这些都是我的真实感受,我在2020年就去有意识的学习了各种设计模式,但一直没有使用场景,也就只停留在了解的层面,知道有这么个概念,直到两个月前,在团队项目中实际看到了同事使用策略模式来解决实际问题的代码,瞬间豁然开朗,原来是这么用的。然后就在图像生成的功能上使用了,好处确实很明显。
除此之外,AIBOT 中在支付功能也是用了策略模式,来实现 会员、图像、单次充值的不同场景。
总结
策略模式虽然好处多多,但技术没有银弹,合适的才是最好的。对于经常变更策略的场景,策略模式确实能够更好的选择,但是如果不加选择的使用策略模式,会不自觉陷入过度设计的陷阱