策略模式是干什么的 、怎么用 ?

定义

策略模式(Strategy Pattern)是一种行为设计模式,它定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户,从而达到算法的变化不会影响到客户。这种模式涉及到三个角色:

  1. 上下文(Context):持有一个策略类的引用,最终给客户端调用。
  2. 策略(Strategy):定义所有支持的算法的公共接口。Context使用这个接口来调用某个Concrete Strategy定义的算法。
  3. 具体策略(Concrete Strategy):实现Strategy接口的具体算法。

使用场景

  1. 当你想让一个对象有多种行为,而且想避免使用大量的条件语句时,可以使用策略模式。它提供了一种用条件语句以外的另一种选择,当你需要根据不同条件执行不同行为时特别有用。比如,一个排序的类,它支持多种排序算法,如冒泡排序、选择排序等,可以通过策略模式来实现这些算法的互换。
  2. 还有就是模拟多种营销类型的时候可以使用策略模式

image.png
营销类型实现的策略模式的简图
image.png

实现步骤

以下是使用Java实现策略模式的基本步骤:

  1. 定义策略接口:这个接口声明了算法的方法。
  2. 创建具体策略类:实现策略接口,封装了相关的算法和行为。
  3. 创建上下文类:持有策略类的引用,通过构造方法或者setter方法来接收策略对象。上下文可能定义了一些策略之间共享的数据。
  4. 客户端调用:客户端创建上下文对象,并传入具体的策略类,通过上下文对象来调用策略方法。

项目中实现的规则过滤

需求:

我们完成的ChatGPT项目, 虽然这里实现了问答的功能, 但是对于一个需要上线的项目来说,还需要必要的管理。 只有这样,你才不会被“约谈“。
因为ChatGPT是外国的东西, 他并不会对我们国内的敏感内容进行过滤, 虽然它本身也实现了相关的过滤功能, 但那是相对而言的, 所以如果你的网站被某些不良用户恶意利用。 到时候网站被强制下线, 损失的可就不是零星半点了。
所以, 我们在这里需要对用户输入的问答内容做敏感词过滤规则。 同时,如果每个用户都能够无限次数的使用你的项目, 那么你这个白菜被割的可就真香了。 所以我们还需要做限制使用次数的规则。再者, 如果如果多个用户在同一时间段内, 一直做问答,那么这个接口的访问粒度就会非常大, 所以我们还得需要做限频限次的操作。

为什么使用策略模式

频次、频率、白名单、敏感词等,都是用于支撑核心业务之外辅助流程,这些流程都是比较容易随着业务的变动而发生变化。所以我们要把这类东西分别封装起来,让它们的改变不会影响到核心业务流程。同时使用工厂服务, 实现业务的统一调用。经过这样一套策略模式 + 工厂服务的,我们实现的业务代码就会显得美观而又实用。

规则过滤的业务逻辑

实现过滤规则

image.png
首先根据工厂服务的特点, 我们首先定义**统一的规则接口(ILogicFilter接口)**, 这样其他模块调用的时候就有了统一的访问地址。

1
2
3
4
5
6
7
8
9
/**
* 规则过滤统一接口
*/
public interface ILogicFilter {
// 规则校验方法, 实现敏感词过滤和访问次数
RuleLogicEntity<ChatProcessAggregate> filter(ChatProcessAggregate chatProcess) throws Exception;

}

接下来,我们实现的两种规则算法就可以继承这个接口

访问次数限制算法

在这里我们通过访问配置文件中的白名单用户(无任何限制的用户), 来进行匹配。 如果未匹配,那么就需要将用户存入限制访问次数的缓存中。 完成问答之后visitCount进行累加。, 最后再通过配置的上下文类进行返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 访问次数过滤
* 通过继承规则过滤接口, 实现规则过滤的次数限制规则,这里我们实现对每个用户限制使用的次数是10次
*/
@Slf4j
@Component
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.ACCESS_LIMIT)
public class AccessLimitFilter implements ILogicFilter {

@Value("${app.config.limit-count:10}")
private Integer limitCount; // 限制访问次数
@Value("${app.config.white-list}")
private String whiteListStr; // 不进行限制次数的白名单用户, 后续需要优化作为数组来实现, 并且不能在配置类中实现
// todo 后续使用数据库之后, 可以通过使用定时任务的方式来做缓存
@Resource
private Cache<String, Integer> visitCache; // 访问次数的缓存

@Override
public RuleLogicEntity<ChatProcessAggregate> filter(ChatProcessAggregate chatProcess) throws Exception {

log.info("访问的用户(Token):{}, openId是:{}", chatProcess.getToken(), chatProcess.getOpenid());
// 1. 对白名单中的用户直接放行
if(chatProcess.isWhiteList(whiteListStr)){
return RuleLogicEntity.<ChatProcessAggregate>builder()
.type(RuleLogicEntity.LogicCheckTypeVO.SUCCESS)
.data(chatProcess)
.build();
}
//2. 如果不是, 那么就先查找用户使用的次数 , 然后计算剩余次数, 最后给用户响应
String openId = chatProcess.getOpenid();
int visitCount = visitCache.get(openId, () -> 0);
// 2.1 判断次数是否超过限制次数
if(visitCount < limitCount) {
visitCache.put(openId, visitCount + 1);
return RuleLogicEntity.<ChatProcessAggregate>builder()
.type(RuleLogicEntity.LogicCheckTypeVO.SUCCESS)
.data(chatProcess)
.build();
}
// 2.2 如果是,直接进行false
return RuleLogicEntity.<ChatProcessAggregate>builder()
.type(RuleLogicEntity.LogicCheckTypeVO.REFUSE)
.data(chatProcess)
.info("您今日的免费 " + limitCount + "次,已耗尽。 请明天再来!!!").build();
}

}

敏感词过滤规则算法

同样的, 首先判断是否为白名单用户, 如果不是再进行规则过滤
通过解析携带用户问答消息的ChatProcessAggregate, 得到用户的问答内容。
接下来通过SensitiveWordBs敏感词库,实现对用户问答Content的检测。如果检测到了敏感词, 那么就直接返回即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 实现规则过滤接口, 判断用户输入的内容是否为敏感词中的内容, 如果是, 那么就拒绝回答
*/
@Slf4j
@Component
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.SENSITIVE_WORD)
public class SensitiveWordFilter implements ILogicFilter {

// 敏感词库中的词
@Resource
private SensitiveWordBs words;

// 还是对白名单中的用户放行
@Value("${app.config.white-list}")
private String whiteListStr;

@Override
public RuleLogicEntity<ChatProcessAggregate> filter(ChatProcessAggregate chatProcess) throws Exception {
// 1. 判断是否为白名单用户, 如果是,那么就放行
if (chatProcess.isWhiteList(whiteListStr)){
return RuleLogicEntity.<ChatProcessAggregate>builder()
.type(RuleLogicEntity.LogicCheckTypeVO.SUCCESS)
.data(chatProcess)
.build();
}
//2. 如果不是 白名单用户, 那么需要进行判断
//2.1 创建一个新的ChatProcessAggregate, 然后获取用户会话携带的所有信息
ChatProcessAggregate newChatProcessAggregate = new ChatProcessAggregate();
newChatProcessAggregate.setOpenid(chatProcess.getOpenid());
newChatProcessAggregate.setModel(chatProcess.getModel());

//2.2 获取用户输入的内容, 进行判断
List<MessageEntity> newMessages = chatProcess.getMessages().stream()
.map(message -> {
String content = message.getContent();
String checkedMessage = words.replace(content);
return MessageEntity.builder()
.content(checkedMessage)
.role(message.getRole())
.name(message.getName())
.build();
}).collect(Collectors.toList());

newChatProcessAggregate.setMessages(newMessages);
// 2.3 将敏感词进行过滤之后, 返回新的用户聊天信息的聚合ChatProcessAggregate
return RuleLogicEntity.<ChatProcessAggregate>builder()
.type(RuleLogicEntity.LogicCheckTypeVO.SUCCESS)
.data(newChatProcessAggregate)
.build();
}
}

工厂服务暴露规则

我们这里使用的工厂服务是简单工厂模式中提供的工厂服务,他的作用是:

  • 集中管理:将规则类的管理集中到一个位置
  • 解耦:客户端与具体产品的创建过程解耦,客户端不需要知道如何创建对象,只需要知道工厂类。

当然我们这里的统一管理并不是通过if-else这种方式实现, 而不同过map存储。 同时在不同的规则算法上通过自定义注解 LogicStrategy 将各类规则通过工厂的方式进行处理并对外提供服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @description 规则工厂
* 实现规则过滤接口所需要实现的内容
*/
@Service
public class DefaultLogicFactory {

public Map<String, ILogicFilter> logicFilterMap = new ConcurrentHashMap<>();

public DefaultLogicFactory(List<ILogicFilter> logicFilters) {
// 构造函数使用logicFilters列表上的forEach方法来遍历每个ILogicFilter实例。
// 并且查找每个实例上的LogicStrategy注解, 。如果找到了注解(即strategy不为null)
// 则将该逻辑过滤器实例以注解中指定的代码(strategy.logicMode().getCode())作为键添加到logicFilterMap映射中。
logicFilters.forEach(logic -> {
LogicStrategy strategy = AnnotationUtils.findAnnotation(logic.getClass(), LogicStrategy.class);
if (null != strategy) {
logicFilterMap.put(strategy.logicMode().getCode(), logic);
}
});
}
// 将获取的注解内容暴露出去, 供服务访问
public Map<String, ILogicFilter> openLogicFilter() {
return logicFilterMap;
}


}

在业务中使用规则

应答服务业务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@Slf4j
public abstract class AbstractChatService implements IChatService {

@Resource
protected OpenAiSession openAiSession;

@Override
public ResponseBodyEmitter completions(ResponseBodyEmitter emitter, ChatProcessAggregate chatProcess) {
try {
emitter.onCompletion(() -> {
log.info("流式问答请求完成,使用模型:{}", chatProcess.getModel());
});

emitter.onError(throwable -> log.error("流式问答请求疫情,使用模型:{}", chatProcess.getModel(), throwable));

// 1. 校验权限
// 在authController中已经实现了

// 2. 规则过滤
RuleLogicEntity<ChatProcessAggregate> ruleLogicEntity = this.doCheckLogic(chatProcess,
LogicModel.ACCESS_LIMIT.getCode(),
LogicModel.SENSITIVE_WORD.getCode());
// 规则校验失败, 直接返回。 不进行问答
if (!LogicCheckTypeVO.SUCCESS.equals(ruleLogicEntity.getType())) {
emitter.send(ruleLogicEntity.getInfo());
emitter.complete();
return emitter;
}
// 3. 应答处理
// todo 请求问答这里
this.doMessageResponse(ruleLogicEntity.getData(), emitter);
} catch (Exception e) {
throw new ChatGPTException(Constants.ResponseCode.UN_ERROR.getCode(), Constants.ResponseCode.UN_ERROR.getInfo());
}
// 4. 返回结果
return emitter;
}
//检查提供的规则过滤逻辑
protected abstract RuleLogicEntity<ChatProcessAggregate> doCheckLogic(ChatProcessAggregate chatProcess, String... logics) throws Exception;

protected abstract void doMessageResponse(ChatProcessAggregate chatProcess, ResponseBodyEmitter responseBodyEmitter) throws JsonProcessingException;

}

规则过滤调用:

1
2
3
4
5
6
7
8
9
10
// 2. 规则过滤
RuleLogicEntity<ChatProcessAggregate> ruleLogicEntity = this.doCheckLogic(chatProcess,
LogicModel.ACCESS_LIMIT.getCode(),
LogicModel.SENSITIVE_WORD.getCode());
// 规则校验失败, 直接返回。 不进行问答
if (!LogicCheckTypeVO.SUCCESS.equals(ruleLogicEntity.getType())) {
emitter.send(ruleLogicEntity.getInfo());
emitter.complete();
return emitter;
}

doCheckLogic规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
*/
@Service
public class ChatService extends AbstractChatService {
@Resource
private DefaultLogicFactory logicFactory;
@Override
protected RuleLogicEntity<ChatProcessAggregate> doCheckLogic(ChatProcessAggregate chatProcess, String... logics) throws Exception {
Map<String, ILogicFilter> logicFilterMap = logicFactory.openLogicFilter();
RuleLogicEntity<ChatProcessAggregate> entity = null;
for (String code : logics) {
entity = logicFilterMap.get(code).filter(chatProcess);
if (!LogicCheckTypeVO.SUCCESS.equals(entity.getType())) return entity;
}
return entity != null ? entity : RuleLogicEntity.<ChatProcessAggregate>builder()
.type(LogicCheckTypeVO.SUCCESS).data(chatProcess).build();
}

过滤规则实现所需model

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 用户聊天信息的聚合
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ChatProcessAggregate {
/** 用户ID */
private String openid;
/** 验证信息 */
private String token;
/** 默认模型 */
private String model = ChatGPTModel.GPT_3_5_TURBO.getCode(); // 默认给出的是3.5模型, 但是后续我们还需要集成chatglm
/** 问题描述 */
private List<MessageEntity> messages;

public boolean isWhiteList(String whiteListStr) {
String[] whiteList = whiteListStr.split(Constants.SPLIT);
for (String whiteOpenid : whiteList) {
if (whiteOpenid.equals(openid)) return true;
}
return false;
}

}

1
2
3
4
5
6
7
8
9
10
11
/**
* 通过自定义注解 LogicStrategy 将各类规则通过工厂的方式进行处理并对外提供服务。
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogicStrategy {

LogicModel logicMode();

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 规则校验结果实体
*
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class RuleLogicEntity<T> {

private LogicCheckTypeVO type; // 规则校验类型
private String info; // 规则校验信息
private T data;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @description 逻辑校验类型,值对象
*/
@Getter
@AllArgsConstructor
public enum LogicCheckTypeVO {

SUCCESS("0000", "校验通过"),
REFUSE("0001","校验拒绝"),
;

private final String code;
private final String info;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 规则过滤种类的逻辑枚举
*/
@Getter
public enum LogicModel {
ACCESS_LIMIT("ACCESS_LIMIT", "访问次数过滤"),
SENSITIVE_WORD("SENSITIVE_WORD", "敏感词过滤"),
;
private String code;
private String info;


LogicModel(String code, String info) {
this.code = code;
this.info = info;
}

public String getCode() {
return code;
}

public void setCode(String code) {
this.code = code;
}

public String getInfo() {
return info;
}

public void setInfo(String info) {
this.info = info;
}
}

说明

学习自: 小傅哥项目 https://bugstack.cn/md/develop/design-pattern/2020-07-05-%E9%87%8D%E5%AD%A6%20Java%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8A%E5%AE%9E%E6%88%98%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F%E3%80%8B.html