趣味编程之玩转策略模式

160 阅读7分钟

一、前言

各位彦祖们,是不是时常为屎山代码而感到苦恼,为一堆复杂冗余的代码逻辑而问候前辈,尤其是在深夜被领导临时叫醒,去修复bug,翻开前辈那优雅代码,一脸生无可恋地亲切问候他万遍,代码虐你千万遍,你依旧待它如初恋。看来作为一名coders,如何减少被后辈问候的次数,是很有必要滴,下面一起走进趣味编程之策略模式吧!

二、抛砖引玉

1、业务背景

用户在门户网站首页,根据选择小站类型不同,可以访问到不同小站数据,可以在其小站页面进行修改状态,编辑小站资料,以及发起购买,装饰道具等操作。

不同小站页面下,功能点类似,但是触发之后里面的逻辑是有很大的差异性的,举个例子:比如现有两种类型的小站,用户触发操作,根据小站的类型不同,触发的校验规则不一样,产生逻辑操作也有很大的差异,但入参与返回体一致。而且后续进行功能迭代,可能里面的逻辑会经常被修改。

面对这些不确定的因素,在当初已有的屎山上,各位彦祖们,如何在工期紧的情况快速进行迭代与代码重构勒。

没错,就是你心中所想的策略模式,那么下面一起以改变小站状态为例来看看重构之前与重构之后的差异吧!

2、最初版

2.1 DTO

@Getter
@Setter
@ToString
public class ReqDTO implements Serializable {
    /**
     * 会话消息Id
     */
    private String msgId;
    /**
     * 请求头 
     */
    private Header header;
}

@Data
public class ChangeStationDTO extends ReqDTO {

    private static final long serialVersionUID = 1L;
    /**
     *  状态
     */
    private String status;
    /**
     * 类型
     */
    private String type;
    /**
     * 小站编号
     */
    private String stationId;
}

2.2 VO


@Getter
@Setter
@ToString
public class ChangeStationStatusVO implements Serializable {
    private static final long serialVersionUID = 1L;
}

2.3 service


public interface StationService {
    /**
     * 改变小站
     *
     * @param dto ChangeStationDTO
     * @return ChangeStationStatusVO
     */
    ChangeStationStatusVO changeStation(ChangeStationDTO dto);
}

类型枚举:


public enum ChangeStatusStrategyEnum {
    /**
     * 招聘小站
     */
    E_STATION,
    /**
     * 商店小站 
     */
    GOODS_STATION;
}

service实现类:

@Service
public class StationServiceImpl implements StationService {

    @Autowired
    private RedisService redisService;

    @Autowired
    private RecruitmentStation recruitmentStation;

    @Autowired
    private GoodsStation goodsStation;



    @Override
    public ChangeStationStatusVO changeStation(ChangeStationDTO dto) {
        // 获取用户信息
        String userInfo = redisService.getUserInfo(dto.getHeader().getValue(Header.AUTHORIZATION.getValue()), dto.getMsgId());
        if (Objects.equals(ChangeStatusStrategyEnum.E_STATION.name(),dto.getType())){
            // 如果是招聘类型小站
            // 校验用户修改小站的规则
            // 查询招聘小站信息
            String stationInfo = recruitmentStation.getStationInfo(dto.getStationId());
            // 比较小站信息与缓存中用户信息中的小站信息
            // 计算改变招聘小站规则,每天能免费修改几次,
            // 达到条件之后,修改,要如何计算扣取积分规则
            // 更新招聘小站状态,返回结果
            return new ChangeStationStatusVO();
        }

        if (Objects.equals(ChangeStatusStrategyEnum.GOODS_STATION.name(), dto.getType())) {
            // 获取商品小站信息
            String stationInfo = goodsStation.getStationInfo(dto.getStationId());
            // 校验修改商品小站的规则
            // 扣取商店余额,更新小站状态,推送置顶
            // 发送短信,获取积分,等等一系列操作
            return new ChangeStationStatusVO();
        }
       throw new IllegalArgumentException(String.format("类型参数异常,本次请求类型为:%s",dto.getType()));
    }
}

UML类图:

image.png

模拟使用:


@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchApp.class)
public class ClientTest {

    @Autowired
    private StationService stationService;

    @Test
    public void ordinaryUseTest() {
        stationService.changeStation(new ChangeStationDTO());
    }
}

思考一个问题?

在后续业务进行迭代时,可能会存在多种类型小站,业务逻辑经常会被修改,如果后续开发人员依旧按照上述这种类型的写法,会存在什么问题。

在使用策略模式之前,我们需要弄明白,为啥要有设计模式来重构代码,为啥不依照前辈们的代码快速叠屎,这样开发时间短,风险性最小,因为代码逻辑改动量最小,最需要关注需要改动的那块逻辑即可,具有这种思想的coder,往往是忽视的业务的迭代复杂度,随着时间的拉长,你当初的改点都冗余在StationServiceImpl#changeStation中,逻辑分支越来越多,此时业务针对某一些小站类型逻辑发生了很大的改变,这个时候,你重新读这块逻辑的代码时,会情不自禁的问候前辈,尤其是当你想打开idea中git提交记录,看看是那个大聪明这样快速叠屎时,最尴尬的莫过于,看到的自己的名字。

下面,我带各位彦祖们,走进花里胡哨的策略模式玩法,我大概将其分为两种类型的玩法,不过其中心思想没啥变化。

3、Map + interface

3.1 interface

public interface ChangeStatusStrategy {
    /**
     * 改变状态
     */
    ChangeStationStatusVO changeStatus(ChangeStationDTO dto);

    /**
     * 获取策略实现类
     * @param type 类型
     * @return ChangeStatusStrategy
     */
    ChangeStatusStrategy getStrategyService(String type);

}

3.2 abstract类

思考一个问题:定义一个策略抽象类除了内聚各种策略之外,还有什么意义?

public abstract class AbstractChangeStatusStrategy implements ChangeStatusStrategy {

    private static final Map<String, ChangeStatusStrategy> MAP = new HashMap<>(16);

    protected void register(ChangeStatusStrategy changeStatusStrategy, ChangeStatusStrategyEnum strategyEnum){
        MAP.put(strategyEnum.name(),changeStatusStrategy);
    }

    @Override
    public ChangeStatusStrategy getStrategyService(String type) {
        return Optional.ofNullable(MAP.get(type))
                .orElseThrow(() -> new IllegalArgumentException(String.format("获取策略异常,非法类型参数,其值为:%s",type)));
    }

}

3.3 具体策略实现类

招聘小站:


@Service
public class EStationChangeStatus extends AbstractChangeStatusStrategy {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @PostConstruct
    private void init() {
        super.register(this, ChangeStatusStrategyEnum.E_STATION);
    }

    @Override
    public ChangeStationStatusVO changeStatus(ChangeStationDTO dto) {
        // todo 招聘小站逻辑

        return new ChangeStationStatusVO();
    }
}

商店小站:


@Service
public class GoodsStationChangeStatus extends AbstractChangeStatusStrategy {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @PostConstruct
    private void init() {
        super.register(this, ChangeStatusStrategyEnum.GOODS_STATION);
    }


    @Override
    public ChangeStationStatusVO changeStatus(ChangeStationDTO bo) {
        // todo 商店小站逻辑
        
        return new ChangeStationStatusVO();
    }
    
}

UML类图:

image.png

3.4 模拟调用

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchApp.class)
public class ClientTest {

    @Autowired
    private StationService stationService;

    @Qualifier("goodsStationChangeStatus")
    @Autowired
    private ChangeStatusStrategy changeStatusStrategy;

    @Test
    public void ordinaryUseTest() {
        stationService.changeStation(new ChangeStationDTO());
    }

    @Test
    public void mapInterfaceTest(){
        ChangeStationDTO dto = new ChangeStationDTO();
        dto.setType(ChangeStatusStrategyEnum.E_STATION.name());
        ChangeStatusStrategy strategyService = changeStatusStrategy.getStrategyService(dto.getType());
        strategyService.changeStatus(dto);
    }
}

思考一个问题:在进行处理业务逻辑时,上述这种做法,我每次都需要先获取策略,再根据策略调用具体的方法,要写两行代码,能不能更简便一些勒,只需写一行代码即可,从调用方角度来看,我只需要关心入参与出参即可,越简洁越好,那如何设计这样的策略模式勒?

要解决这种问题,我想到了一种玩法,就是利用Map + 函数式 + interface,下面让我们一起见证函数式 + 策略模式这种花里胡哨玩法吧!

4、 Map + 函数式 + interface

在介绍这种玩法时,各位彦祖要先了解一下函数式编程,在这儿咱们不展开细说,主要是利用Function 和 Consumer 函数,当这两种基本函数不满足时,彦祖们可以自行定义个性化函数。

4.1 interface


public interface StoreStrategy {

    String CHANGE_STATUS = "changeStatus";

    String SEND_MESSAGE = "sendMessage";

    <T, R> R apply(String type, String methodName,T t);

    <T> void accept(String type, String methodName, T t);

}

4.2 abstract类

public abstract class AbstractStoreStrategy implements StoreStrategy {

    private static final Map<String,Map<String,Function>> FUNCTION_MAP = new HashMap<>(16);

    private static final Map<String,Map<String, Consumer>> CONSUMER_MAP = new HashMap<>(16);


    protected <T, R> void registeredFunction(ChangeStatusStrategyEnum strategyEnum, String methodName, Function<T, R> function) {
        Map<String, Function> functionMap = FUNCTION_MAP.get(strategyEnum.name());
        if (Objects.isNull(functionMap)) {
            functionMap = new HashMap<>(16);
        }
        functionMap.put(methodName, function);
        FUNCTION_MAP.put(strategyEnum.name(), functionMap);
    }

    protected <T> void registeredConsumer(ChangeStatusStrategyEnum strategyEnum, String methodName, Consumer<T> consumer) {
        Map<String, Consumer> consumerMap = CONSUMER_MAP.get(strategyEnum.name());
        if (Objects.isNull(consumerMap)) {
            consumerMap = new HashMap<>(16);
        }
        consumerMap.put(methodName, consumer);
        CONSUMER_MAP.put(strategyEnum.name(), consumerMap);
    }

    @Override
    public <T, R> R apply(String type, String methodName, T t) {
        Map<String, Function> functionMap = Optional.ofNullable(FUNCTION_MAP.get(type))
                .orElseThrow(() -> new IllegalArgumentException(String.format("类型为,%s获取到的值为空", type)));
        return (R) Optional.ofNullable(functionMap.get(methodName))
                .orElseThrow(() -> new IllegalArgumentException(String.format("方法名为:%s,获取到的值为空", methodName)))
                .apply(t);
    }


    @Override
    public <T> void accept(String type, String methodName, T t) {
        Map<String, Consumer> consumerMap = Optional.ofNullable(CONSUMER_MAP.get(type))
                .orElseThrow(() -> new IllegalArgumentException(String.format("类型为,%s获取到的值为空", type)));
        Optional.ofNullable(consumerMap.get(methodName))
                .orElseThrow(() -> new IllegalArgumentException(String.format("方法名为:%s,获取到的值为空", methodName)))
                .accept(t);
    }

    protected abstract ChangeStationStatusVO changeStatus(ChangeStationDTO dto);


    protected abstract void sendMessage(ChangeStationDTO dto);

}

4.3 具体策略实现类

商店小站策略类:

@Service
public class OtherSmallStationStoreImpl extends AbstractStoreStrategy {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @PostConstruct
    private void init(){
        super.registeredConsumer(ChangeStatusStrategyEnum.GOODS_STATION,SEND_MESSAGE,this::sendMessage);
        super.registeredFunction(ChangeStatusStrategyEnum.GOODS_STATION,CHANGE_STATUS,this::changeStatus);
    }


    @Override
    protected ChangeStationStatusVO changeStatus(ChangeStationDTO dto) {
        logger.info("======>>>>>>changeStatus bo is {}", dto);
        return new ChangeStationStatusVO();
    }

    @Override
    protected void sendMessage(ChangeStationDTO dto) {
        logger.info("======>>> sendMessage bo is {}", dto);
    }
}

招聘小站策略类:

@Service
public class SmallStationStoreImpl extends AbstractStoreStrategy {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @PostConstruct
    private void init() {
        super.registeredFunction(ChangeStatusStrategyEnum.E_STATION, CHANGE_STATUS, this::changeStatus);
        super.registeredConsumer(ChangeStatusStrategyEnum.E_STATION, SEND_MESSAGE, this::sendMessage);
    }


    @Override
    public ChangeStationStatusVO changeStatus(ChangeStationDTO dto) {
        logger.info("====>>>>>ChangeStatusBO is {}", dto);
        return new ChangeStationStatusVO();
    }

    @Override
    protected void sendMessage(ChangeStationDTO dto) {
        logger.info("====>>>>>ChangeStatusBO is {}", dto);
    }


}

UML类图:

image.png

4.4 模拟使用

@RunWith(SpringRunner.class)
@SpringBootTest(classes = ElasticsearchApp.class)
public class ClientTest {

    @Autowired
    private StationService stationService;

    @Qualifier("goodsStationChangeStatus")
    @Autowired
    private ChangeStatusStrategy changeStatusStrategy;

    @Qualifier("smallStationStoreImpl")
    @Autowired
    private StoreStrategy storeStrategy;
    
    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Test
    public void ordinaryUseTest() {
        stationService.changeStation(new ChangeStationDTO());
    }

    @Test
    public void mapInterfaceTest(){
        ChangeStationDTO dto = new ChangeStationDTO();
        dto.setType(ChangeStatusStrategyEnum.E_STATION.name());
        ChangeStatusStrategy strategyService = changeStatusStrategy.getStrategyService(dto.getType());
        strategyService.changeStatus(dto);
    }
    
    @Test
    public void functionTest(){
        ChangeStationDTO dto = new ChangeStationDTO();
        dto.setType(ChangeStatusStrategyEnum.E_STATION.name());
        ChangeStationStatusVO vo = storeStrategy.apply(dto.getType(), StoreStrategy.CHANGE_STATUS, dto);
        logger.info("======>>> vo is {}",vo);
    }
    
    @Test
    public void consumerTest(){
        ChangeStationDTO dto = new ChangeStationDTO();
        dto.setType(ChangeStatusStrategyEnum.E_STATION.name());
        storeStrategy.accept(dto.getType(),StoreStrategy.SEND_MESSAGE,dto);
    }
}

三、总结

相信各位彦祖看完上述案例之后,是否已经对策略模式有大概的了解勒,是不是想着磨刀霍霍向屎山了勒,模式虽好,切记不可乱用哟,过度的设计往往只会让维护更加困难,毕竟代码是给人看的,最后,希望各位彦祖们的代码少被后辈者问候。