前言
早在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容器管理的实现类都注入了进来。
这里再理一下策略模式的整理思路
- 注入所有的实现类
- 将所有的实现类以唯一标识(Type注解的值)为KEY,实例本身为VALUE放置到Map中
- 通过客户端的请求标识去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);
// ....
}
}
- 测试结果
至此,策略模式分享完毕!
结语
非常感谢你读到最后,希望本文对你有所帮助,有问题敬请留意交流,你的支持你的点赞你的关注是我努力的动力!!!