1 适用场景
平时写代码,if else是经常用到的语法,是针对不同的条件,运行不同的代码。在条件分支不多的情况下,这肯定是绝佳的选择,但如果分支过多,需要写几十甚至上百的if-else,不仅可读性不好,也不利于维护。这时,可以用到策略模式。本篇文章介绍的策略模式不仅可以省去if-else的代码,还可以将不同的策略内容封装到不同的Class中,在调用不同的策略时也十分方便简洁。
2 实现思路
实现思路是这样的,将不同的策略的代码内容封装到不同的Class里面,并声明自定义注解,在每一个策略类的类名上都加上这一注解,不同的策略的注解中给不同的值;在Spring项目启动时,通过类扫描器扫描注解将对应的Class写入Spring中;需要调用具体的策略方式时,再通过从Spring的上下文对象ApplicationContext获取Class去执行不同的策略中的内容。
3 code
直接上代码了。
3.1 自定义注解
自定义注解用来标在不同的策略类上。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyType {
String value() default "";
}
3.2 类扫描器
类扫描器相当于一个工具类,用来扫描指定包中的Class,在网上有许多类扫描器的写法,这里提供一个。
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.SystemPropertyUtils;
import org.springframework.util.TypeUtils;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* 策略扫描器
*/
public class ClassScanner {
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private final List<TypeFilter> includeFilters = new ArrayList<>();
private final List<TypeFilter> excludeFilters = new ArrayList<>();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
/**
* 添加包含的Fiter
* @param includeFilter TypeFilter
*/
public void addIncludeFilter(TypeFilter includeFilter) {
this.includeFilters.add(includeFilter);
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackage 包名
* @param targetTypes 需要指定的目标类型,可以是pojo,可以是注解
* @return Set<Class<?>>
*/
public static Set<Class<?>> scan(String basePackage, Class<?>... targetTypes) {
ClassScanner cs = new ClassScanner();
for (Class<?> targetType : targetTypes){
if (TypeUtils.isAssignable(Annotation.class, targetType)) {
cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));
} else {
cs.addIncludeFilter(new AssignableTypeFilter(targetType));
}
}
return cs.doScan(basePackage);
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackage 包名
* @return Set<Class<?>>
*/
public Set<Class<?>> doScan(String basePackage) {
Set<Class<?>> classes = new HashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(
SystemPropertyUtils.resolvePlaceholders(basePackage))+"/**/*.class";
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
if (resource.isReadable()) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if ((includeFilters.size() == 0 && excludeFilters.size() == 0)|| matches(metadataReader)) {
try {
classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
} catch (ClassNotFoundException ignore) {}
}
}
}
} catch (IOException ex) {
throw new RuntimeException("IO Exception!", ex);
}
return classes;
}
/**
* 处理 excludeFilters和includeFilters
* @param metadataReader MetadataReader
* @return boolean
*/
private boolean matches(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return true;
}
}
return false;
}
}
3.3 实现BeanFactoryPostProcessor接口
实现Spring提供的这一接口的目的是为了在Spring项目启动时,通过类扫描器去扫描注解,并将对应的Class写入Spring。
import com.bigsea.myannotation.MyType;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class HandlerProcessor implements BeanFactoryPostProcessor {
/**
* 扫描的目标类所处的包的路径
*/
private final static String basePackage = "com.bigsea.strategy";
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Map<String, Class> map = new HashMap<>();
for (Class<?> aClass : ClassScanner.scan(basePackage, MyType.class)) {
String type = aClass.getAnnotation(MyType.class).value();
map.put(type,aClass);
}
beanFactory.registerSingleton(MyType.class.getName(), map);
}
}
3.4 从Spring上下文对象中获取Class对象的类
这个类的作用就是结合之前的功能用来从Spring的上下文中获取对应的策略的Class。
import com.bigsea.myannotation.MyType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 策略上下文,从Spring中得到Class
*/
@Component
public class HandlerContext {
@Autowired
private ApplicationContext applicationContext;
public AbstractHandler getInstance(String type) {
Map<String, Class> map = (Map<String, Class>) applicationContext.getBean(MyType.class.getName());
return (AbstractHandler)applicationContext.getBean(map.get(type));
}
}
3.5 定义传递值的类
这一对象用来封装数据,根据type值来判断调用哪一个策略(这一个type对应MyType中的值);并且可以在这一个对象中定义其他属性,传递属性值来实现业务功能。
/**
* 传递数据的对象
*/
public class MyDetail {
private String type;
private String name;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3.6 抽象类
这一抽象类的作用是让每一个策略类都去继承它,并实现当中的方法,在调用策略处,直接调用它,来执行不同的策略。
/**
* 抽象类
*/
public abstract class AbstractHandler {
/**
* 根据不同的类型处理内容
* @param orderInfo MyDetail
*/
public abstract void handle(MyDetail orderInfo);
}
3.7 策略
策略可以有很多个,写几十、几百,甚至几千个都可以,根据具体的业务需要来。这里我写了一个策略来举例子。
import com.bigsea.myannotation.MyType;
import com.bigsea.strategy.AbstractHandler;
import com.bigsea.strategy.MyDetail;
import org.springframework.stereotype.Component;
/**
* 策略一
*/
@Component
@MyType("1")
public class DetailOneStrategy extends AbstractHandler {
@Override
public void handle(MyDetail myDetail) {
// todo 这里可以写业务代码,需要的参数内容可以从myDetail中获取
System.out.println("type:" + myDetail.getType());
System.out.println("name:" + myDetail.getName());
}
}
4 测试结果
我们用Junit进行测试,执行的结果是调用了DetailOneStrategy中的handle方法,因为这一个策略类的@MyType给的值是"1",而在测试方法中我给的type的值也是"1"。
import com.bigsea.strategy.AbstractHandler;
import com.bigsea.strategy.HandlerContext;
import com.bigsea.strategy.MyDetail;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class StrategyTest {
@Autowired
private HandlerContext handlerContext;
@Test
public void testStrategy() {
// todo 这里可以前端传过来的获取从数据库查出来的数据去构造对象,然后调用策略方法
MyDetail myDetail = new MyDetail();
myDetail.setType("1");
myDetail.setName("一号");
AbstractHandler handler = handlerContext.getInstance(myDetail.getType());
handler.handle(myDetail);
}
}
打印控制台的结果:
type:1
name:一号