由浅入深理解策略模式

由浅入深理解策略模式,第1张

目录

前言

一.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;
    }
}

类似于这样简单的业务需求,这样写肯定没有问题。实际开发过程中,需要处理的需求往往比较复杂,这样简单的策略,不一定能支撑起来,再者也不符合SOLID的开闭原则,对扩展开发,对修改关闭。 

二. 接口+Map实现策略模式

第一种方式,每当我们需要新增/修改一个运算规则时,都需要直接改动 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;
    }
}

可以看到,把operator抽象成接口,不同的运算分别对应operator接口的实现类;可以看到只需要添加实现类,维护运算符和实现类的映射关系,就可以完成这个功能的扩展,一定程度上满足SOLID的开闭原则,也使用面向对象开发的思维。

三.函数式编程+Lambda表达式实现策略模式 

上述的例子中,方式二感觉很契合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;
    }
}

这种方式,把原本的面向对象开发转变为面向函数开发,也只适合用在不太复杂的业务需求。另外,若想详细了解java8函数式编程,可以参考https://blog.csdn.net/wwyywwsaber/article/details/124649175

四.注解+Manager实现策略模式 

回顾第二种方式,开发者新增其他的运算时,需要做两步

  1. 新增实现类
  2. 维护Map中的映射关系

维护就会有风险,比如误删的风险;这时必然需要进一步思考,如何省去第二步,让开发者只关注接口的实现。这里详细介绍如何改进第二种方式。

1.新增运算符声明注解 
@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;
    }
}

这样设计的策略模式,基于计算器这个功能,就拥有两个核心类 Calculator和 OperatorManager ,这两个类基本不会做任何变动。开发者只需要知道调用Calculator 的calculate 就能使用该功能;开发者想要扩展只需要实现Operator接口加上OperatorIdentity 注释就能进行扩展。

五.使用业务核心作为Base进一步优化

了解spring后置处理器的话,可以想象,大型项目中spring管理的bean成千上万,postProcessAfterInitialization方法会扫描每一个bean,造成性能损耗。

思考一下,怎么解决?

这里讲解在第四种方式基础上如何缩小扫描范围,锁定到该功能的实现类。

 1.修改OperatorManager类,不再使用spring后置处理器
@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思维。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/877835.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-13
下一篇 2022-05-13

发表评论

登录后才能评论

评论列表(0条)

保存