知识点
问题描述
在大多小公司,没有较好的业务规划和技术架构,随着业务迭代、需求变更,会积累大量的条件判断逻辑。比如一个接口在不同 App 版本执行不同业务、对不同接口参数进行不同解析统计、对接 N 家 M 个商家收款账号等等。都会造成大量的 if-else 代码堆积,维护时影响心情,特别是让“我”维护。
++如何优化如下代码++:
private void handle01(HttpServletRequest request) { ... }
private void handle02(HttpServletRequest request) { ... }
private void handle03(HttpServletRequest request) { ... }
private void handle04(HttpServletRequest request) { ... }
private void handle05(HttpServletRequest request) { ... }
private void handleDefault(HttpServletRequest request) { ... }
public void demo(String uri, HttpServletRequest request) {
if("/a/user".equals(uri)){
handle01(request);
} else if("/b/document_list".equals(uri)){
handle02(request);
} else if("/b/content_list".equals(uri)){
handle03(request);
} else if("/b/ext_list".equals(uri)){
handle04(request);
} else if("/b/xxx".equals(uri)){
handle05(request);
} else {
handleDefault(request);
}
}
复制代码
方案
认识策略模式
策略模式指:某个行为在不同的场景中,需要使用不同的算法。我们将不同的算法称之为策略,在不同的环境中,获取不同的策略进行执行。
如题:我们会处理每个请求,根据每个请求的 uri 不同,采用不同的算法。这个行为就是接口请求,不同场景就是不同的 uri。所以我们可以使用策略模式去优化 if-else 代码。
封装处理函数
定义策略接口,让所有策略都通过 exec 方法执行算法。
public interface Strategy{
void exec(HttpServletRequest request);
}
复制代码
定义各个算法实现类,实现 Strategy 接口 exec 方法。
public class H1 implements Strategy{
@Override
public void exec(HttpServletRequest request) { }
}
public class H2 implements Strategy{
@Override
public void exec(HttpServletRequest request) { }
}
复制代码
这样那些逻辑类,就可以修改为如下方式,只判断使用哪个策略,具体执行算法,在每个实现类中实现,整个类的作用也更加明确。
public void demo(String uri, HttpServletRequest request) {
Strategy strategy = null;
if("/a/user".equals(uri)){
strategy = new H1();
} else if("/b/document_list".equals(uri)){
strategy = new H2();
} else if("/b/content_list".equals(uri)){
strategy = new H3();
} else if("/b/ext_list".equals(uri)){
strategy = new H4();
}
if (strategy != null){
strategy.exec(request);
}
}
复制代码
可结合工厂模式获取策略对象。
项目应用
假设需求为 ==/ly/mysql、/ly/redis、/ly/es== 3 个接口需要将请求日志实时存储到==mysql、redis、elasticsearch== 3 种不同数据库中。
定义一个注解,作为实现类的标识
/** 定义注解 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Uri {
String[] value();
}
复制代码
定义接口和实现类。为了方便工厂类的读取,建议实现类放到同一个包中(如:com.example.strategy.impl)。工厂类会在初始化时通过反射创建对象。
/** 策略接口 */
public interface Strategy{
void write(HttpservletRequest rquest);
}
/** MySql实现 */
@Uri("/ly/mysql")
public class MySqlStrategy implements Strategy{
public void write(HttpservletRequest request){
log.info("写入mysql成功");
}
}
/** Redis实现 */
@Uri("/ly/redis")
public class RedisStrategy implements Strategy{
public void write(HttpservletRequest request){
log.info("写入Redis成功")
}
}
/** Es实现 */
@Uri("/ly/es")
public class ESStrategy implements Strategy{
public void write(HttpservletRequest request){
log.info("写入ES成功")
}
}
复制代码
定义工厂类,在项目启动时,初始化工厂类。为每个 uri 创建对应的策略对象,方便使用。
public class StrategyFactory{
private ApiBuryPointFactory(){}
//保存每个uri的策略对象,临时实现,如果策略存在并发问题,这样存储不可行
private static final Map<String,Strategy> StrategyMap = new ConcurrentHashMap<>();
/** 获取uri策略对象 */
public static Strategy getStrategy(String uri){
if(uri == null){
return null;
}
return StrategyMap.get(uri);
}
//初始化
static {init();}
/** 初始化,获取包 com.example.strategy.impl包下的class文件,创建对象 */
@SneakyThrows
private static void init() {
final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
final Resource[] resources = resolver.getResources("classpath:com/example/strategy/impl/*.class");
if(resources.length == 0){
return;
}
for (Resource resource : resources) {
final String className = new SimpleMetadataReaderFactory().getMetadataReader(resource).getClassMetadata().getClassName();
final Class<?> clazz = Class.forName(className);
if(!clazz.isAnnotationPresent(Uri.class)){
continue;
}
//获取注解
final Uri annotation = clazz.getAnnotation(Uri.class);
final String[] uris = annotation.value();
if(uris == null || uris.length < 1){
continue;
}
final Object instance = clazz.newInstance();
if (!(instance instanceof ApiBuryPointTactics)) {
continue;
}
for (String uri : uris) {
TacticsMAP.put(uri, (ApiBuryPointTactics) instance);
}
log.info("url={},策略={}", uris, className);
}
}
}
复制代码
这样我们就可以在拦截器或者过滤器中通过 StrategyFactory.getStrategy(String)方法获取策略对象并执行 write()方法。
uri 相关基本都通过正则匹配,可在工厂方法中通过正则、类正则方法匹配 uri,获取策略对象。
// 使用AntPathMatcher匹配uri
public static Strategy getStrategy(String uri){
if(uri == null){
return null;
}
final AntPathMatcher matcher = new AntPathMatcher();
for (Map.Entry<String, Strategy> entry : StrategyMap.entrySet()) {
if(matcher.match(entry.getKey(), uri)){
return entry.getValue();
}
}
return null;
}
复制代码
评论