JAVA开发工作总结

JAVA设计模式-策略模型

2023-01-17  本文已影响0人  暴走的狐狸

策略模型定义

Strategy Policy
通用定义:定义了一组算法,将每个算法封装起来,并使它们可以互换,策略算法允许算法独立于使用它的客户而变化。
通俗的讲,一个具体的事物可以通过多种不同的方式和行为来达到目的,因此这些相应的行为可以组合在一起,在不同的条件下进行任意的替换和使用。
比如:下单支付,最终都是扣款,但支付的方式会分为信用卡,借记卡,第三方支付等,而信用卡下面,可能有招商银行,工商银行,每个银行的信用卡支付手续费等都有差异,就可以使用策略模式进行组合。
或者,对外推送数据,不同的外部平台要求的数据规则不一样,但内部其实是同一份数据,针对外部需要的数据,我们可以按外部平台分为不同的平台策略,按不同规则封装数据进行推送。

策略模型的使用场景

三个以上的if else场景,可以使用策略模式

多个switch case场景,可以使用策略模式

领域设计模型中,认为有些非技术层面,但在业务领域的一些过程行为,是有业务价值的,而当对多个过程进行选择时,选择的复杂性和过程本身的复杂性,就会使得系统和业务耦合。因此,我们需要把容易变的部分抽取到一个单独的策略对象中。将规则和它所控制的行为区分开。按照策略模式来实现规则和过程的可替换。

这样的业务场景很多:
比如支付场景
折扣计算场景
股票选股策略
技术层面
不同的日志采用不同的策略进行记录和处理
不同的请求按不同的数据路由规则路由到内部其他交互的系统等等
总而言之,当你发现一个系统中的一些对象,它们的区别仅仅是一些行为的不同,或者一个服务需要根据条件动态匹配不同算法,或者一个对象,有多个不同的行为,都可以考虑策略模式。

策略模型的实现方式

策略模式的实现包含三个基本对象
1 策略实现类本身
StrategyA,StrategyB...
2 Stratey接口,所有的具体策略都实现这个接口
3 一个策略查找或者适配的服务类 Context或者叫StrategyAdpater都可以

我们看下具体步骤,上代码
这里我拿实践用例举例,我们有一个日志服务,专门用于日志记录,名字叫ediLog,然后针对不同的日志类型,我们要做不同的处理,
举两个日志类型:
一 下单日志类型,不同平台的订单,要记录不同的关键字段。比如字节下单,我们要把字节的相关字段转化为我们统一的订单日志记录字段。拼多多下单,同理。
二 对外推送日志,这里会包含平台要求的脱敏信息使用日志,短信回执日志等。都按不同的逻辑进行记录。
数据的消费,我们通过filebeat采集,丢同一个的日志kafak,然后日志服务消费这个消息,并进行相应的策略适配。这里由于我们知道不同的类型的数据要做什么处理,因此采用传入一个唯一的key来查找策略类的模式。

第一步定义一个日志策略接口
LogStrategy

/**
 * @author TAN Fox
 * 2023/1/17 14:07
 * 日志服务处理的策略模型
 */
public interface EdiLogStrategy {

    /**
     * 根据具体配置的枚举resource,进入不同的策略
     *
     * @return
     */
    String matchHandlerResource();

    /**
     * 存储具体的日志
     *
     * @param ediLogDTO
     */
    void save(EdiLogDTO ediLogDTO);
}

第二步 添加一个枚举对象,并实现一个具体的策略类
字节订单策略具体实现

/**
 * 电子面单错误报文数据
 *
 * @author TAN Fox
 * 2023/1/17 14:07
 */
@Service
@Slf4j
public class BytedanceEbillStrategy implements EdiLogStrategy {
    @Autowired
    EbillNormalService ebillNormalService;

    @Override
    public String matchHandlerResource() {
        return EdiLogResourceEnum.BYTEDANCE_EBILL.getResource();
    }

   @Override
    public void save(EdiLogDTO ediLogDTO) {
       // 进行具体策略实现,这里就是差异化的地方,也可以继续封装和加工
        ebillNormalService.save(ediLogDTO);
    }
}

枚举对象

/**
 * @author TAN Fox
 * 2023/1/17 14:07
 * 日志资源枚举类,用于策略适配,各种的策略,确保资源唯一
 */
public enum EdiLogResourceEnum {
    /**
     * 字节电子面日志
     */
    BYTEDANCE_EBILL("ebillError", "字节电子面日志"),
    SMS_ERROR_LOG("smsErrorLog", "短信错误日志");

    private final String resource;

    private final String description;

    EdiLogResourceEnum(String resource, String description) {
        this.resource = resource;
        this.description = description;
    }

    public String getResource() {
        return resource;
    }

    public String getDescription() {
        return description;
    }
}

第三步 策略查找适配器

/**
 * @author TAN Fox
 * 2023/1/17 14:07
 * 策略匹配
 */
@Component
public class EdiLogStrategyAdapter {

    private final Map<String, EdiLogStrategy> ediLogStrategyMap;

    /**
     * 批量初始化配置的策略模型
     * @param strategyList
     */
    public EdiLogStrategyAdapter(List<EdiLogStrategy> strategyList) {
        ediLogStrategyMap = new HashMap<>();
        strategyList.forEach(strategy -> ediLogStrategyMap.put(strategy.matchHandlerResource(),strategy));
    }

    public EdiLogStrategy matchEdiLogStrategy(String resource) {
        EdiLogStrategy strategy = ediLogStrategyMap.get(resource);
        if (strategy == null) {
            throw new BizException("通过resource未匹配到具体的日志策略模型:" + resource);
        }
        return strategy;
    }

}

第四步 使用

**
 * 
 * 日志信息消费者,并调用策略模型适配策略
 *
 * @author 01403533
 * @date 2021/1/6 15:27
 */
@Slf4j
@Component("ediSelfLogKafKaListener")
public class EdiSelfLogKafKaListener implements IStringMessageConsumeListener {
    @Autowired
    EdiLogStrategyAdapter ediLogStrategyAdapter;

    @Override
    public void onMessage(List<String> list) {
        //降级开关
        if (DisConfMessageLogPropertiesReload.getEdiLogTotalSwitchOff()) {
            return;
        }
        //处理kafka发送过来的消息
        for (String logStr : list) {
            try {
                EdiLogDTO ediLogDTO;
                ediLogDTO = JsonUtil.fromJson(logStr, EdiLogDTO.class);
                EdiLogStrategy ediLogStrategy = ediLogStrategyAdapter.matchEdiLogStrategy(ediLogDTO.getResource());
                ediLogStrategy.save(ediLogDTO);
                log.info("ediLogDTO:{}", ediLogDTO);
            } catch (Exception e) {
                //单独日志文件
                log.error("日志处理异常,", e);
            }
        }
    }
}

当然这里是因为实际情况需要,我给每个策略配了一个resource来区分。你也可以有不同的方法来匹配策略,比如直接注入或者枚举类型载入都可以。
看我单元测试示范代码

/**
 * @author TAN Haifeng
 * 2021/11/4 15:29
 */
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
@Profile("test")
public class IsvSumTraceSchedulerTest  {
    @Autowired
    MonitorRuleAdapter monitorRuleAdapter;

    @Test
    public void testStrategy() {
        assertNotNull(monitorRuleAdapter);
        IsvSumTraceQueryCheckStrategy isvSumTraceQueryCheckStrategy = new IsvSumTraceQueryCheckStrategy();
        MonitorRuleAdapter monitorRuleAdapter = new MonitorRuleAdapter(isvSumTraceQueryCheckStrategy);
        MonitorRuleEntity monitorRuleEntity = new MonitorRuleEntity();
        boolean result = monitorRuleAdapter.check(monitorRuleEntity,isvSumTraceQueryCheckStrategy); //可枚举,JobRuleMapping
        boolean result2 = monitorRuleAdapter.check(monitorRuleEntity, JobRuleMapping.ISV_TRACE_QUERY_SUM);
        assertFalse(result,"测试策略模型,直接存入");
        assertFalse(result2,"测试测试模型,枚举类型载入");
    }
}

UML类图

UML类图

使用心得:

1 策略模式,可以封装策略,但具体的策略何时何地使用,由调用方自己决定
2 策略模式可以灵活扩展和新增策略方法,修改原策略方法,提供更好的灵活性
3 解决单一原则问题
4 一些通用的行为,也可以继续抽象封装,用abstract抽象
5 使用方必须理解不同策略的区别,并了解什么时候使用

上一篇 下一篇

猜你喜欢

热点阅读