在有一些业务代码里面,写了一堆if-else,而里面的业务代码都在做差不多的事情。典型的例子就像以前的社保系统,人员异动的时候根据异动类型来判断,然后到不同的方法里面去执行,有可能方法还有重载方法,导致整个类的代码非常多,下一个人来维护的时候非常困难。
    所以,根据我在青海项目里面写的一个方法,然后分析了一下参投保系统的人员异动代码,有了以下优化:
    1、策略模式
    其主要目的是将重复的业务代码,写成一个“策略”,然后根据传入不同的对象,去调用。就像在参投保系统里面,人员新参保,暂停参保,终止参保等这些异动类型,都可以写成一个策略,写在不同的类里面,就可以起到第一阶段的解耦作用,再也不用在同一个类里面找方法了。一个类就做一件事,就非常符合”单一职则”原则,而同时一个异动类型要修改,就去修改某个指定的类,在后期的维护上,也是非常方便的。
    2、工厂模式
    工厂模式在这里,是用来构建对象,但是在这个demo里面,主要是用来消除if-else

    下面就用代码来演示

    1. /**
    2. * @description 策略接口,用来定义有多少种策略,比如在广铁系统里面,可以定义 异动,异动提交/审核,异动受理
    3. * @author: huangyeqin
    4. * @create : 2020/12/14 8:36
    5. */
    6. public interface IPersonChangeStrategy<T> extends InitializingBean {
    7. // 异动申请: T 表示从前台传过来的数据 bizType表示异动类型
    8. void apply(T t,String bizType);
    9. // 异动审核 :特别说明,一个异动,从申请到审核受理,最好用一个对象类接受数据
    10. void audit(T t,String bizType);
    11. // 异动受理
    12. void submit(T t,String bizType);
    13. }
    14. /**
    15. * @description 策略工厂
    16. 这个类的目的,是用来初始化策略实现类的
    17. * @author: huangyeqin
    18. * @create : 2020/12/14 16:01
    19. */
    20. public class IPersonChnageFactory {
    21. private static Map<String, IPersonChangeStrategy> strategyMap = Maps.newHashMap();
    22. public static IPersonChangeStrategy getInvokeStrategy(String name) {
    23. return strategyMap.get(name);
    24. }
    25. public static void register(String name, IPersonChangeStrategy strategy) {
    26. if (StringUtils.isEmpty(name) || null == strategy) {
    27. throw new HygeiaException(404,"请传入正确的类型");
    28. }
    29. strategyMap.put(name, strategy);
    30. }
    31. }
    32. /**
    33. * @description 策略上下文,用来执行策略
    34. * @author: huangyeqin
    35. * @create : 2020/12/14 8:38
    36. */
    37. public class AuthContext<T> {
    38. private IPersonChangeStrategy strategy;
    39. public AuthContext(IPersonChangeStrategy strategy){
    40. this.strategy = strategy;
    41. }
    42. // ... 这个是暴露出去的接口,所以这里可以将很多方法都放在这个方法里面执行,就成了模板方法模式
    43. public void apply(T t,String bizType){
    44. strategy.apply(t,bizType);
    45. }
    46. //... 省略了 审核、受理的方法
    47. }
    48. /**
    49. * @description 具体的策略实现类
    50. * 这里有个条件,需要加载spring框架。因为没有spring框架,这些类是没办法交给spring去管理,所以不能提前初始化,那么策略工厂就无法获取到这个类
    51. * @author: huangyeqin
    52. * @create : 2020/12/14 15:55
    53. */
    54. @Component
    55. public class AuthAlterationStrategy implements IPersonChangeStrategy<PmmAlterationOptAddDTO> {
    56. @Override
    57. public void doHandler(PmmAlterationOptAddDTO pmmAlterationOptAddDTO, String bizType) {
    58. }
    59. // 要注意的是,所有的异动类型,都需要提前维护一个枚举类,如果没有枚举类,也可以维护一个 常量类,
    60. @Override
    61. public void afterPropertiesSet() throws Exception {
    62. IPersonChnageFactory.register(WorkFlowEnum.PMM003.getFlowCode(),this);
    63. }
    64. }
    65. // .... 可以写多个策略实现类
    66. // 开始调用策略
    67. public class MainTest{
    68. public static void main(String[] args){
    69. // 在调用的时候,bizType传入一个异动类型就可以,object是从前端来的参数,所以前端所有的方法,都可以调用这个接口了,
    70. // 真正的实现了所有的异动,都是一个入口,甚至以后的异动,都只需要一个按钮,想要做什么异动,在下拉框中选一个异动类型,然后填数据,
    71. // 可以做到让用户少判断,所有的逻辑都写在策略类里面
    72. IPersonChangeStrategy strategy = IAuthFactory.getInvokeStrategy(bizType);
    73. strategy.doHandler(object, bizType);
    74. }
    75. }

    以上就是策略模式+工厂模式在广铁项目中的使用。
    说完了使用,再说说优缺点:
    优点
    (1)逻辑解耦,再也不需要在一个类里面写几十个方法了,甚至重载的方法一大堆。
    (2)符合单一职责原则,就是一个类就负责一件事。
    (3)符合开闭原则;如果想要增加一个业务类型,直接加一个策略类就可以了。比如说广州市社保局要加一个退休延缴的异动类型,那么直接加一个策略类,然后专注的去实现业务代码;
    (4)代码逻辑清晰,再也不会点一个方法跳来跳去,甚至很多时候根本找不到方法在哪里。
    缺点:
    (1)类变多了,但出现几十个异动类型的时候(当然一般不会有这么多,最多就十几个),会写很多类,但这也是有好处的,已经这么大一个项目了,多写几个类也不影响什么。
    (2)要解决事务问题,因为参投保系统的这个框架不熟悉,不知道事务是在哪里配置的,所以要先解决事务问题,不然程序执行中断的时候,事务不会回滚,会造成数据错误。但这个如果解决了以后就不是问题。
    (3)最关键的一点,就是逻辑不会消失,只会从一个地方转移到另一个地方。


    上面的代码是很完美的,但是没有考虑一个问题,如果没有spring环境该怎么办?
    没有spring环境的话,代码确实无法做到那样完美,但也不是没有办法解决的,spring的InitializingBean 主要是在初始化的时候,将策略放到map集合中,那么只要做到类似的效果就行了,下面我就来推荐一种枚举类的写法。

    1. /**
    2. * @description 策略模式枚举类
    3. * @author: huangyeqin
    4. * @create : 2020/12/16 10:19
    5. */
    6. public enum StrategyEnum {
    7. ZHAOYUN_STRATEGY("ConcreteStrategy",new ConcreteStrategy()),
    8. GUANYU_STRATEGY("Concrete2Strategy",new Concrete2Strategy());
    9. private IStrategy strategy;
    10. private String name;
    11. StrategyEnum(String name,IStrategy strategy){
    12. this.name = name;
    13. this.strategy = strategy;
    14. }
    15. public IStrategy getStrategy() {
    16. return strategy;
    17. }
    18. public void setStrategy(IStrategy strategy) {
    19. this.strategy = strategy;
    20. }
    21. public String getName() {
    22. return name;
    23. }
    24. public void setName(String name) {
    25. this.name = name;
    26. }
    27. /**
    28. * 根据name获取strategy的值
    29. * @Desc :
    30. * @Author : huangyeqin
    31. * @Date : 2020/12/16 10:26
    32. * @Param : code
    33. * @Result : com.outlets.design.strategy.IStrategy
    34. */
    35. public static IStrategy getStrategyByName(String code){
    36. for(StrategyEnum platformFree:StrategyEnum.values()){
    37. if(code.equals(platformFree.getName())){
    38. return platformFree.getStrategy();
    39. }
    40. }
    41. return null;
    42. }
    43. }

    上面的枚举类中,将name设置为key,策略设置为value,也实现了map的效果。
    那么在客户端中如何去调用呢?

    1. /**
    2. * @description 客户端
    3. * @author: huangyeqin
    4. * @create : 2020/12/16 10:09
    5. */
    6. public class Client {
    7. public static void main(String[] args) {
    8. String name = "zhaoyun";
    9. StrategyContext context = new StrategyContext(StrategyEnum.getStrategyByName(name));
    10. context.handler();
    11. }
    12. }

    同样的效果,也不用去写那么多的if-else了。
    写了这么多,并不是说不推荐用if-else,而是不推荐在哪里都用if-else,特别是当代码块特别长的时候,要考虑一下接手这个代码的人的心情。