目录
前言
一.if/else实现策略模式
二. 接口+Map实现策略模式
三.函数式编程+Lambda表达式实现策略模式
四.注解+Manager实现策略模式
五.使用业务核心作为Base进一步优化
总结
前言
一.if/else实现策略模式策略模式在开发过程中经常使用,具体表现为一个类的行为或其算法在运行时可以更改,理解起来就是运行时多态。这里用一个例子由浅入深、拨云见雾去理解策略模式。
例子:实现一个简单的计数器功能,只考虑双目运算。剖析这个例子,输入的数据有左运算数、运算符、右运算数。
@Component
public class Calculator {
public int calculate(int left,String operator,int right){
if("+".equals(operator)){
return left+right;
}else if("-".equals(operator)){
return left-right;
}else if("*".equals(operator)){
return left*right;
}else if("/".equals(operator)){
return left/right;
}
return -1;
}
}
二. 接口+Map实现策略模式类似于这样简单的业务需求,这样写肯定没有问题。实际开发过程中,需要处理的需求往往比较复杂,这样简单的策略,不一定能支撑起来,再者也不符合SOLID的开闭原则,对扩展开发,对修改关闭。
第一种方式,每当我们需要新增/修改一个运算规则时,都需要直接改动 Calculator 类的代码,因此该类中的运算逻辑也需要进行测试覆盖,对维护来说,指数上升。
稍微思考一下,可以发现,这个功能的逻辑变化点是 operator 参数,那么是不是可以把 operator 抽象出来进行设计呢?
public interface Operator {
int calculate(int left,int right);
}
public class PlusOperator implements Operator{
@Override
public int calculate(int left, int right) {
return left+right;
}
}
public class SubOperator implements Operator{
@Override
public int calculate(int left, int right) {
return left-right;
}
}
public class MultOperator implements Operator{
@Override
public int calculate(int left, int right) {
return left*right;
}
}
public class DivOperator implements Operator{
@Override
public int calculate(int left, int right) {
return left/right;
}
}
@Component
public class Calculator {
private static Map operatorMap = new HashMap<>();
static{
operatorMap.put("+",new PlusOperator());
operatorMap.put("-",new SubOperator());
operatorMap.put("*",new MultOperator());
operatorMap.put("/",new DivOperator());
}
public int calculate(int left,String operator,int right){
return operatorMap.get(operator)!=null?
operatorMap.get(operator).calculate(left,right):-1;
}
}
三.函数式编程+Lambda表达式实现策略模式可以看到,把operator抽象成接口,不同的运算分别对应operator接口的实现类;可以看到只需要添加实现类,维护运算符和实现类的映射关系,就可以完成这个功能的扩展,一定程度上满足SOLID的开闭原则,也使用面向对象开发的思维。
上述的例子中,方式二感觉很契合java开发的思想,但给人一种冗重感;就是说代码量太多了,一定程度上阻碍开发者使用这种方式,而使得开发者更倾向于第一种方式。这种情景,可以用函数式接口代替运算符接口,Lambda表达式代替实现类。
@Component
public class Calculator {
private static Map> operatorMap = new HashMap<>();
static{
operatorMap.put("+",(a,b) -> a+b);
operatorMap.put("-",(a,b) -> a-b);
operatorMap.put("*",(a,b) -> a*b);
operatorMap.put("/",(a,b) -> a/b);
}
public int calculate(int left,String operator,int right){
return operatorMap.get(operator)!=null?operatorMap.get(operator).apply(left,right):-1;
}
}
四.注解+Manager实现策略模式这种方式,把原本的面向对象开发转变为面向函数开发,也只适合用在不太复杂的业务需求。另外,若想详细了解java8函数式编程,可以参考https://blog.csdn.net/wwyywwsaber/article/details/124649175
1.新增运算符声明注解回顾第二种方式,开发者新增其他的运算时,需要做两步
- 新增实现类
- 维护Map中的映射关系
维护就会有风险,比如误删的风险;这时必然需要进一步思考,如何省去第二步,让开发者只关注接口的实现。这里详细介绍如何改进第二种方式。
@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface OperatorIdentity {
String operator();
}
2.各个类添加运算符声明注解,兵交由spring管理
@Component
@OperatorIdentity(operator = "+")
public class PlusOperator implements Operator {
@Override
public int calculate(int left, int right) {
return left+right;
}
}
@Component
@OperatorIdentity(operator = "*")
public class MultOperator implements Operator {
@Override
public int calculate(int left, int right) {
return left*right;
}
}
@Component
@OperatorIdentity(operator = "-")
public class SubOperator implements Operator {
@Override
public int calculate(int left, int right) {
return left-right;
}
}
@Component
@OperatorIdentity(operator = "/")
public class DivOperator implements Operator {
@Override
public int calculate(int left, int right) {
return left/right;
}
}
3.新增Manager类,继承BeanPostProcessor接口重写postProcessAfterInitialization方法
@Component
public class OperatorManager implements BeanPostProcessor {
private static final Map operatorMap = new HashMap<>();
public Operator getOperatorImpl(String operator){
return operatorMap.get(operator);
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
OperatorIdentity identity = getOperatorIdentity(bean);
if(identity!=null){
String operator = identity.operator();
if(operatorMap.containsKey(operator)){
throw new RuntimeException("实现类重复! operator:"+operator+" bean:"+beanName);
}
operatorMap.put(operator,(Operator)bean);
}
return bean;
}
private OperatorIdentity getOperatorIdentity(Object bean){
return bean.getClass().getAnnotation(OperatorIdentity.class)!=null?
bean.getClass().getAnnotation(OperatorIdentity.class):null;
}
}
4.重写Calculator类
@Component
public class Calculator {
@Autowired
OperatorManager manager;
public int calculate(int left,String operator,int right){
Operator operatorImpl = manager.getOperatorImpl(operator);
return operatorImpl!=null?operatorImpl.calculate(left,right):-1;
}
}
五.使用业务核心作为Base进一步优化这样设计的策略模式,基于计算器这个功能,就拥有两个核心类 Calculator和 OperatorManager ,这两个类基本不会做任何变动。开发者只需要知道调用Calculator 的calculate 就能使用该功能;开发者想要扩展只需要实现Operator接口加上OperatorIdentity 注释就能进行扩展。
1.修改OperatorManager类,不再使用spring后置处理器了解spring后置处理器的话,可以想象,大型项目中spring管理的bean成千上万,postProcessAfterInitialization方法会扫描每一个bean,造成性能损耗。
思考一下,怎么解决?
这里讲解在第四种方式基础上如何缩小扫描范围,锁定到该功能的实现类。
@Component
public class OperatorManager{
private static final Map operatorMap = new HashMap<>();
public BaseOperator getOperatorImpl(String operator){
return operatorMap.get(operator);
}
public void registerOperator(Object bean) throws BeansException {
OperatorIdentity identity = getOperatorIdentity(bean);
if(identity!=null){
String operator = identity.operator();
if(operatorMap.containsKey(operator)){
throw new RuntimeException("实现类重复! operator:"+operator+" bean:"+bean.getClass().getName());
}
operatorMap.put(operator,(BaseOperator)bean);
}
}
private OperatorIdentity getOperatorIdentity(Object bean){
return bean.getClass().getAnnotation(OperatorIdentity.class)!=null?
bean.getClass().getAnnotation(OperatorIdentity.class):null;
}
}
2.以业务核心创建抽象类并继承Operator接口,使用@PostConstruct注解锁定实现类范围
public abstract class BaseOperator implements Operator{
@Autowired
OperatorManager manager;
@PostConstruct
public void init(){
manager.registerOperator(this);
}
}
3.调整实现类,使其继承BaseOperator抽象类并交给spring管理
@Component
@OperatorIdentity(operator = "+")
public class PlusOperator extends BaseOperator {
@Override
public int calculate(int left, int right) {
return left+right;
}
}
@Component
@OperatorIdentity(operator = "-")
public class SubOperator extends BaseOperator {
@Override
public int calculate(int left, int right) {
return left-right;
}
}
@Component
@OperatorIdentity(operator = "*")
public class MultOperator extends BaseOperator {
@Override
public int calculate(int left, int right) {
return left*right;
}
}
@Component
@OperatorIdentity(operator = "/")
public class DivOperator extends BaseOperator {
@Override
public int calculate(int left, int right) {
return left/right;
}
}
总结重点在@PostConstruct注解,java自带的注解,可以这样理解:在类的方法上添加了该注解后,该类构造器执行后会调用该注解下的方法。而子类实例化过程中会调用父类中的@PostConstruct方法。具体源码没仔细研究,对其原理有了解的欢迎留言!
策略模式,重点是把业务核心抽象出来,根据场景进行不同的编码架构,摆脱繁重的if/else判断,尽量遵循SOLID原则和OOAD思维。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)