在有一些业务代码里面,写了一堆if-else,而里面的业务代码都在做差不多的事情。典型的例子就像以前的社保系统,人员异动的时候根据异动类型来判断,然后到不同的方法里面去执行,有可能方法还有重载方法,导致整个类的代码非常多,下一个人来维护的时候非常困难。
所以,根据我在青海项目里面写的一个方法,然后分析了一下参投保系统的人员异动代码,有了以下优化:
1、策略模式
其主要目的是将重复的业务代码,写成一个“策略”,然后根据传入不同的对象,去调用。就像在参投保系统里面,人员新参保,暂停参保,终止参保等这些异动类型,都可以写成一个策略,写在不同的类里面,就可以起到第一阶段的解耦作用,再也不用在同一个类里面找方法了。一个类就做一件事,就非常符合”单一职则”原则,而同时一个异动类型要修改,就去修改某个指定的类,在后期的维护上,也是非常方便的。
2、工厂模式
工厂模式在这里,是用来构建对象,但是在这个demo里面,主要是用来消除if-else
下面就用代码来演示
/**
* @description 策略接口,用来定义有多少种策略,比如在广铁系统里面,可以定义 异动,异动提交/审核,异动受理
* @author: huangyeqin
* @create : 2020/12/14 8:36
*/
public interface IPersonChangeStrategy<T> extends InitializingBean {
// 异动申请: T 表示从前台传过来的数据 bizType表示异动类型
void apply(T t,String bizType);
// 异动审核 :特别说明,一个异动,从申请到审核受理,最好用一个对象类接受数据
void audit(T t,String bizType);
// 异动受理
void submit(T t,String bizType);
}
/**
* @description 策略工厂
这个类的目的,是用来初始化策略实现类的
* @author: huangyeqin
* @create : 2020/12/14 16:01
*/
public class IPersonChnageFactory {
private static Map<String, IPersonChangeStrategy> strategyMap = Maps.newHashMap();
public static IPersonChangeStrategy getInvokeStrategy(String name) {
return strategyMap.get(name);
}
public static void register(String name, IPersonChangeStrategy strategy) {
if (StringUtils.isEmpty(name) || null == strategy) {
throw new HygeiaException(404,"请传入正确的类型");
}
strategyMap.put(name, strategy);
}
}
/**
* @description 策略上下文,用来执行策略
* @author: huangyeqin
* @create : 2020/12/14 8:38
*/
public class AuthContext<T> {
private IPersonChangeStrategy strategy;
public AuthContext(IPersonChangeStrategy strategy){
this.strategy = strategy;
}
// ... 这个是暴露出去的接口,所以这里可以将很多方法都放在这个方法里面执行,就成了模板方法模式
public void apply(T t,String bizType){
strategy.apply(t,bizType);
}
//... 省略了 审核、受理的方法
}
/**
* @description 具体的策略实现类
* 这里有个条件,需要加载spring框架。因为没有spring框架,这些类是没办法交给spring去管理,所以不能提前初始化,那么策略工厂就无法获取到这个类
* @author: huangyeqin
* @create : 2020/12/14 15:55
*/
@Component
public class AuthAlterationStrategy implements IPersonChangeStrategy<PmmAlterationOptAddDTO> {
@Override
public void doHandler(PmmAlterationOptAddDTO pmmAlterationOptAddDTO, String bizType) {
}
// 要注意的是,所有的异动类型,都需要提前维护一个枚举类,如果没有枚举类,也可以维护一个 常量类,
@Override
public void afterPropertiesSet() throws Exception {
IPersonChnageFactory.register(WorkFlowEnum.PMM003.getFlowCode(),this);
}
}
// .... 可以写多个策略实现类
// 开始调用策略
public class MainTest{
public static void main(String[] args){
// 在调用的时候,bizType传入一个异动类型就可以,object是从前端来的参数,所以前端所有的方法,都可以调用这个接口了,
// 真正的实现了所有的异动,都是一个入口,甚至以后的异动,都只需要一个按钮,想要做什么异动,在下拉框中选一个异动类型,然后填数据,
// 可以做到让用户少判断,所有的逻辑都写在策略类里面
IPersonChangeStrategy strategy = IAuthFactory.getInvokeStrategy(bizType);
strategy.doHandler(object, bizType);
}
}
以上就是策略模式+工厂模式在广铁项目中的使用。
说完了使用,再说说优缺点:
优点
(1)逻辑解耦,再也不需要在一个类里面写几十个方法了,甚至重载的方法一大堆。
(2)符合单一职责原则,就是一个类就负责一件事。
(3)符合开闭原则;如果想要增加一个业务类型,直接加一个策略类就可以了。比如说广州市社保局要加一个退休延缴的异动类型,那么直接加一个策略类,然后专注的去实现业务代码;
(4)代码逻辑清晰,再也不会点一个方法跳来跳去,甚至很多时候根本找不到方法在哪里。
缺点:
(1)类变多了,但出现几十个异动类型的时候(当然一般不会有这么多,最多就十几个),会写很多类,但这也是有好处的,已经这么大一个项目了,多写几个类也不影响什么。
(2)要解决事务问题,因为参投保系统的这个框架不熟悉,不知道事务是在哪里配置的,所以要先解决事务问题,不然程序执行中断的时候,事务不会回滚,会造成数据错误。但这个如果解决了以后就不是问题。
(3)最关键的一点,就是逻辑不会消失,只会从一个地方转移到另一个地方。
上面的代码是很完美的,但是没有考虑一个问题,如果没有spring环境该怎么办?
没有spring环境的话,代码确实无法做到那样完美,但也不是没有办法解决的,spring的InitializingBean 主要是在初始化的时候,将策略放到map集合中,那么只要做到类似的效果就行了,下面我就来推荐一种枚举类的写法。
/**
* @description 策略模式枚举类
* @author: huangyeqin
* @create : 2020/12/16 10:19
*/
public enum StrategyEnum {
ZHAOYUN_STRATEGY("ConcreteStrategy",new ConcreteStrategy()),
GUANYU_STRATEGY("Concrete2Strategy",new Concrete2Strategy());
private IStrategy strategy;
private String name;
StrategyEnum(String name,IStrategy strategy){
this.name = name;
this.strategy = strategy;
}
public IStrategy getStrategy() {
return strategy;
}
public void setStrategy(IStrategy strategy) {
this.strategy = strategy;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 根据name获取strategy的值
* @Desc :
* @Author : huangyeqin
* @Date : 2020/12/16 10:26
* @Param : code
* @Result : com.outlets.design.strategy.IStrategy
*/
public static IStrategy getStrategyByName(String code){
for(StrategyEnum platformFree:StrategyEnum.values()){
if(code.equals(platformFree.getName())){
return platformFree.getStrategy();
}
}
return null;
}
}
上面的枚举类中,将name设置为key,策略设置为value,也实现了map的效果。
那么在客户端中如何去调用呢?
/**
* @description 客户端
* @author: huangyeqin
* @create : 2020/12/16 10:09
*/
public class Client {
public static void main(String[] args) {
String name = "zhaoyun";
StrategyContext context = new StrategyContext(StrategyEnum.getStrategyByName(name));
context.handler();
}
}
同样的效果,也不用去写那么多的if-else了。
写了这么多,并不是说不推荐用if-else,而是不推荐在哪里都用if-else,特别是当代码块特别长的时候,要考虑一下接手这个代码的人的心情。