if(condition){} else{}太low了,别人都用策略模式

2019-06-292142

前言

早在17年,在一个机缘巧合下,我接触到了游戏开发,初出茅庐的我就是一个货真价值的菜鸟,面对庞杂的请求协议,我只会用if else去找他们对应的业务实例。毋庸置疑,这无论是从代码的优雅性而言,还是健壮性、可扩展性、可维护性而论,成本都是巨大的。接手的人更是想沿着网线爬过来打人。幸运的是,后面来了一位技术大神,他给我们带来很多东西。接下来我们要聊的策略模式就是其中一个。废话不多说,直接进入正题!

业务场景及实现

这里先跟大家道个歉,由于涉及到具体的业务隐私,所以不方便贴出源码。笔者将举水果的例子来跟大家分享策略模式。虽说不是原场景,但五脏俱全绝对不会影响本次内容的分享。先贴出各实体之间的关系

实体继承实现关系

这里先给出Apple的代码

package com.mcd.strategy.bean;

import com.mcd.strategy.annotation.Type;
import com.mcd.strategy.interfac.IFruit;
import com.mcd.strategy.interfac.ITypeValue;
import org.apache.http.Header;
import org.springframework.stereotype.Service;

import java.util.Arrays;

/**
 * @Description
 * @Author LW-mochengdong
 * @Date 2019/6/29 11:53
 **/
@Service
@Type(ITypeValue.APPLE)
public class Apple implements IFruit {

    @Override
    public void handler(Header[] headers) {
        System.out.println(Arrays.toString(headers));
        //todo Apple handler
    }
}
  • 说明

    • @Type 是自定义注解,用来唯一标识具体的实现类

        package com.mcd.strategy.annotation;
      
        import java.lang.annotation.*;
      
        @Documented
        @Target(ElementType.TYPE)
        @Retention(RetentionPolicy.RUNTIME)
        public @interface Type {
            String value();
        }
      
      • ITypeValue是一个接口,用来定义唯一标识的

          package com.mcd.strategy.interfac;
        
          /**
           * @Description
           * @Author LW-mochengdong
           * @Date 2019/6/29 11:45
           **/
          public interface ITypeValue {
        
              String APPLE = "apple";
        
              String ORANGE = "orange";
        
              String BANANA = "banana";
        
          }
    • IFruit 是自定义接口,用来定义水果这种类型

        package com.mcd.strategy.interfac;
      
        import org.apache.http.Header;
      
        /**
         * @Description
         * @Author LW-mochengdong
         * @Date 2019/6/29 11:51
         **/
        public interface IFruit {
      
            void handler(Header[] headers);
      
        }
      
    • Apple、Banana、Orange是具体水果

        package com.mcd.strategy.bean;
      
        import com.mcd.strategy.annotation.Type;
        import com.mcd.strategy.interfac.IFruit;
        import com.mcd.strategy.interfac.ITypeValue;
        import org.apache.http.Header;
        import org.springframework.stereotype.Service;
      
        import java.util.Arrays;
      
        /**
         * @Description
         * @Author LW-mochengdong
         * @Date 2019/6/29 11:53
         **/
        @Service
        @Type(ITypeValue.BANANA)
        public class Banana implements IFruit {
      
            @Override
            public void handler(Header[] headers) {
                System.out.println(Arrays.toString(headers));
                //todo Banana handler
            }
        }

      Orange的实现跟Apple、Banana一样,这里就不再给出代码了。接着进入主题,如果需要根据客户端请求的标识,去获取对应的业务方法,继而执行。这时候你是什么实现的?我想,好多初学者都跟我一样,简单粗暴直接用if else,代码如下

    @Resource
    private ApplicationContext ctx;
    public void if_else(HttpRequestBase request){
        //根据url获取具体方法名 restful 风格
        String path = request.getURI().getPath();
        String method = path.substring(path.lastIndexOf("/") + 1);

        //multiple if_else
        IFruit iFruit = null;
        if(ITypeValue.APPLE.equals(method)){
            iFruit = ctx.getBean(Apple.class);
        } else if (ITypeValue.BANANA.equals(method)){
            iFruit = ctx.getBean(Banana.class);
        } else {
            // .....
        }
        Assert.notNull(iFruit, "target class not exist....");

        //调用具体业务方法
        iFruit.handler(request.getAllHeaders());
    }

这个代码的弊端,估计大家都知道。业务小,这种实现完全没问题,但业务量大的时候,基本上所有不好的词汇都可以用来形容它,比如不便于维护,不好扩展、代码臃肿等等。这个问题的首选解决方案,我想就是策略模式了吧,具体实现如下

    private Map<String, IFruit> handelMap = new HashMap<>();

    @Resource
    private List<IFruit> handleList;

    /**
     * @Description 初始化实例,将实例通过键值对的方式放入到一个Map中
     * @Return void
     * @Author LW-mochengdong
     * @Date 2019/6/29 15:25
     */
    @PostConstruct
    public void init(){
        for (IFruit iFruit : handleList) {
            String value = iFruit.getClass().getAnnotation(Type.class).value();
            if(handelMap.containsKey(value)){
                throw new RuntimeException("this class already exist...");
            }
            handelMap.put(value, iFruit);
        }
    }

    public void execute(HttpRequestBase request) {
        //根据url获取具体方法名 restful 风格
        String path = request.getURI().getPath();
        String method = path.substring(path.lastIndexOf("/") + 1);

        //根据方法名获取具体实例
        IFruit iFruit = handelMap.get(method);
        Assert.notNull(iFruit, "target class not exist....");

        //调用具体业务方法
        iFruit.handler(request.getAllHeaders());
    }

代码的说明,在注释里已有,这里不再累述,问题的关键在于健壮的

@Resource
private List<IFruit> handleList;

给我们提供了强大的便利,这也是本文的精华之一,我想这个功能好多人都不知道,这种方式可以将IFruit的所有交给spring容器管理的实现类都注入了进来

这里再理一下策略模式的整理思路

  1. 注入所有的实现类
  2. 将所有的实现类以唯一标识(Type注解的值)为KEY,实例本身为VALUE放置到Map中
  3. 通过客户端的请求标识去Map里获取对应实例,继而实行

测试

  • 测试代码
package com.mcd.strategy;

import com.mcd.strategy.init.InitBean;
import com.mcd.strategy.interfac.ITypeValue;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.net.URISyntaxException;

@RunWith(SpringRunner.class)
@SpringBootTest
public class StrategyModelApplicationTests {

    @Resource
    private InitBean initBean;

    @Test
    public void test_if_else() throws URISyntaxException {
        URIBuilder ub = new URIBuilder();
        ub.setPath("http://127.0.0.1/"+ ITypeValue.APPLE);
        HttpGet httpGet = new HttpGet(ub.build());
        httpGet.addHeader("parameter", ITypeValue.APPLE);

        //apple 的请求
        initBean.if_else(httpGet);

        ub.setPath("http://127.0.0.1/"+ ITypeValue.BANANA);
        httpGet = new HttpGet(ub.build());
        httpGet.addHeader("parameter", ITypeValue.BANANA);

        //banana 的请求
        initBean.if_else(httpGet);

        // ....
    }

    @Test
    public void testExecute() throws URISyntaxException {
        URIBuilder ub = new URIBuilder();
        ub.setPath("http://127.0.0.1/"+ ITypeValue.APPLE);
        HttpGet httpGet = new HttpGet(ub.build());
        httpGet.addHeader("parameter", ITypeValue.APPLE);

        //apple 的请求
        initBean.execute(httpGet);

        ub.setPath("http://127.0.0.1/"+ ITypeValue.BANANA);
        httpGet = new HttpGet(ub.build());
        httpGet.addHeader("parameter", ITypeValue.BANANA);

        //banana 的请求
        initBean.execute(httpGet);

        // ....
    }

}
  • 测试结果

测试结果

至此,策略模式分享完毕!

结语

非常感谢你读到最后,希望本文对你有所帮助,有问题敬请留意交流,你的支持你的点赞你的关注是我努力的动力!!!

分享
点赞4
打赏
上一篇:Docker常用命令笔记(一)
下一篇:翻译 | java 并发编程:线程封闭