怎么理解策略模式?
当一个需求有很多不同方法实现时就可以采取策略模式。策略模式是一种设计模式,定义了一组算法,并将每个算法封装起来,使它们可以互换使用。策略模式让算法的变化独立于使用算法的客户。
在策略模式中,有一个上下文类负责使用策略类。策略类定义了算法的接口,而具体的算法实现则由具体的策略类来实现。这样,上下文类就可以在运行时根
总结为三部分组成:
- 顶层抽象接口:公共业务方法抽取,条件方法抽取
- 策略实现类:每个业务有独立的一套算法支持
- 策略上下文管理类:用来收集策略类,调用策略类等
案例演示
需求:我们都知道 SpringMVC 中的 Controller 参数有很多不同类型注解修饰,每个参数都会有一个专门解析类进行解析。现在假设有一个 show() 方法,如下:
public static void show(@RequestBody String type
,@RequestParam Integer age
,@RequestHeader Long length) {
}
它有三个参数被不同的注解修饰,现在要对这三个参数进行解析,先不适用策略模式,代码如下:
public class IfElsePolicyTest {
public static void main(String[] args) {
try {
Method method = IfElsePolicyTest.class.getMethod("show", String.class, Integer.class, Long.class);
MethodParameter parameter = new MethodParameter(method, 0);
if (parameter.hasParameterAnnotation(PathVariable.class)) {
// ...逻辑处理
System.out.println(">>>>>>解析含有 @PathVariable 注解的参数");
} else if (parameter.hasParameterAnnotation(RequestParam.class)) {
// ...逻辑处理
System.out.println(">>>>>>解析含有 @RequestParam 注解的参数");
} else if (parameter.hasParameterAnnotation(RequestBody.class)) {
// ...逻辑处理
System.out.println(">>>>>>解析含有 @RequestBody 注解的参数");
} else {
// ...其他逻辑
System.out.println(">>>>>>解析其他逻辑");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void show(@RequestBody String type, @RequestParam Integer age, @RequestHeader Long length) {
}
}
上述代码中借助了 Spring 工具类 MethodParameter,它可以更便捷获取到 show() 方法所有相关信息。从代码书写来看,不用策略模式,if-else 看起来特别多,而且如果参数上有新注解加进来,又得修改 if-else 比较麻烦,代码看起来也不舒服。代码冗余扩展性不好。
现在通过策略模式进行改造,怎么改造呢?遵从前面说的那三部分分析。
顶层抽象接口:
总的来说就是要解析参数,所以公共业务部分就是解析参数,先命名为 resolve() 方法,方法入参根据实际情况定,一般是封装成 DTO、Wrapper 等组合对象,这里简单就传入 Object 类型。返回值一般也是 DTO、Wrapper 包装对象,这里直接就返回 Object 对象,然后再定义策略条件,只有满足这个条件才能使用当前策略,最终定义出来顶层接口,代码如下:
public interface ParamResolver {
public boolean supportType(MethodParameter parameter);
public Object resolve(Object object) throws Exception;
}
策略实现类:
然后实现该接口,在子类中对不同的参数进行解析处理,比如:针对有 @RequestParam 注解修饰的参数的解析,代码如下:
public class RequestParamParamResolver implements ParamResolver{
@Override
public boolean supportType(MethodParameter parameter) {
return Objects.nonNull(parameter) && parameter.hasParameterAnnotation(RequestParam.class);
}
@Override
public Object resolve(Object object) throws Exception {
System.out.println(">>>>>>解析含有 @RequestParam 注解的参数");
return null;
}
}
针对有 @RequestBody 注解修饰的参数的解析,代码如下:
public class RequestBodyParamResolver implements ParamResolver{
@Override
public boolean supportType(MethodParameter parameter) {
return Objects.nonNull(parameter) && parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object resolve(Object object) throws Exception {
System.out.println(">>>>>>解析含有 @RequestBody 注解的参数");
return null;
}
}
针对有 @PathVariable 注解修饰的参数的解析,代码如下:
public class PathVariableParamResolver implements ParamResolver{
@Override
public boolean supportType(MethodParameter parameter) {
return Objects.nonNull(parameter) && parameter.hasParameterAnnotation(PathVariable.class);
}
@Override
public Object resolve(Object object) throws Exception {
System.out.println(">>>>>>解析含有 @PathVariable 注解的参数");
return null;
}
}
策略上下文管理类:
因为子类会比较多,所以对于这些子类就应该提供一个管理类来管理。而这个管理类通常被称之为 Context 上下文,在这个上下文中会去收集子类,调用子类,代码如下:
public class ParamResolverContext {
private static List<ParamResolver> paramResolvers = new ArrayList<>();
public static void addPolicy(ParamResolver resolver) {
paramResolvers.add(resolver);
}
public static ParamResolver getPolicy(MethodParameter parameter) {
for (ParamResolver paramResolver : paramResolvers) {
if (paramResolver.supportType(parameter)) {
return paramResolver;
}
}
throw new NotFoundSuitablePolicyException("未匹配到合适策略");
}
}
测试类如下:
public class PolicyTest {
public static void main(String[] args) {
try {
Method method = PolicyTest.class.getMethod("show", String.class, Integer.class, Long.class);
MethodParameter parameter = new MethodParameter(method, 1);
ParamResolverContext.addPolicy(new PathVariableParamResolver());
ParamResolverContext.addPolicy(new RequestParamParamResolver());
ParamResolverContext.addPolicy(new RequestBodyParamResolver());
ParamResolver policy = ParamResolverContext.getPolicy(parameter);
Object aNull = policy.resolve("null");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void show(@RequestBody String type, @RequestParam Integer age, @RequestHeader Long length) {
}
}
直接通过 ParamResolverContext.getPolicy()
一行代码就可以对参数进行解析,而之前的会有大量的 if-else 分支,现在通过策略模式把大量的 if-else 消除掉,代码简洁,扩展性也比较好。
最后将上述所有对象交给 Spring 管理,先将三个策略实现加上 @Component 注解,代码如下:
@Component
public class RequestParamParamResolver implements ParamResolver{
@Override
public boolean supportType(MethodParameter parameter) {
return Objects.nonNull(parameter) && parameter.hasParameterAnnotation(RequestParam.class);
}
@Override
public Object resolve(Object object) throws Exception {
System.out.println(">>>>>>解析含有 @RequestParam 注解的参数");
return null;
}
}
@Component
public class PathVariableParamResolver implements ParamResolver{
@Override
public boolean supportType(MethodParameter parameter) {
return Objects.nonNull(parameter) && parameter.hasParameterAnnotation(PathVariable.class);
}
@Override
public Object resolve(Object object) throws Exception {
System.out.println(">>>>>>解析含有 @PathVariable 注解的参数");
return null;
}
}
@Component
public class RequestBodyParamResolver implements ParamResolver{
@Override
public boolean supportType(MethodParameter parameter) {
return Objects.nonNull(parameter) && parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object resolve(Object object) throws Exception {
System.out.println(">>>>>>解析含有 @RequestBody 注解的参数");
return null;
}
}
然后将策略上下文交给 Spring 管理,并实现 InitializingBean, ApplicationContextAware 接口,在 afterPropertiesSet() 方法中将策略子类添加到策略上下文中,实现 ApplicationContextAware 接口是为了能够拿到 BeanFactory 工厂,因为这里要使用到 BeanFactory 的 API,代码如下:
@Component
public class ParamResolverContext implements InitializingBean, ApplicationContextAware {
private ApplicationContext context;
private static List<ParamResolver> paramResolvers = new ArrayList<>();
public static void addPolicy(ParamResolver resolver) {
paramResolvers.add(resolver);
}
public static ParamResolver getPolicy(MethodParameter parameter) {
for (ParamResolver paramResolver : paramResolvers) {
if (paramResolver.supportType(parameter)) {
return paramResolver;
}
}
throw new NotFoundSuitablePolicyException("未匹配到合适策略");
}
@Override
public void afterPropertiesSet() throws Exception {
String[] beanNames = context.getBeanNamesForType(ParamResolver.class);
for (String beanName : beanNames) {
paramResolvers.add(context.getBean(beanName,ParamResolver.class));
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}
}
交给 Spring 管理之后,就算有 @RequestHeader 这样的注解加进来,这些代码都不用改动,只需要针对 @RequestHeader 注解实现对应的策略子类即可。
然后再 Controller 中就可以使用 ParamResolverContext 上下文管理类拿到相对于的策略类,代码如下:
@RestController
public class RequestBodyController {
@PostMapping("/person")
public Person person(@RequestBody Person person) throws Exception {
Method method = RequestBodyController.class.getMethod("person",Person.class);
MethodParameter parameter = new MethodParameter(method, 0);
ParamResolver policy = ParamResolverContext.getPolicy(parameter);
Object resolve = policy.resolve(parameter);
System.out.println(">>>>>>person = " + person);
return person;
}
}
结果输出如下:
>>>>>>解析含有 @RequestBody 注解的参数
>>>>>>person = Person{name='gongwm', age=100}