zl程序教程

您现在的位置是:首页 >  后端

当前栏目

设计模式-策略模式与spring集成实战

2023-09-11 14:18:28 时间

1、概述

在软件开发中,我们也常常会遇到类似的情况,实现某一个功能有多条途径,每一条途径对应一种算法,此时我们可以使用一种设计模式来实现灵活地选择解决途径,也能够方便地增加新的解决途径。

譬如商场购物场景中,有些商品按原价卖,商场可能为了促销而推出优惠活动,有些商品打九折,有些打八折,有些则是返现10元等。

而优惠活动并不影响结算之外的其他过程,只是在结算的时候需要根据优惠方案结算

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

2、定义

该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

3、角色

Context(环境类)

环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。

Strategy(抽象策略类)

它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。

ConcreteStrategy(具体策略类)

它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。

4、实现

抽象策略类

public interface Strategy {

    public void process();
}

具体策略类

public class StrategyImpl1 implements Strategy {
    @Override
    public void process() {
        System.out.println("StrategyImpl1");
    }
}

public class StrategyImpl2 implements Strategy {
    @Override
    public void process() {
        System.out.println("StrategyImpl2");
    }
}

环境类

public class StrategyContext {

    Strategy strategy;

    public StrategyContext(Strategy strategy) {
        this.strategy = strategy;
    }

    public void deal() {
        strategy.process();
    }
}

测试

class Client{
     public static void main(String[] args) {
            StrategyContext context1 = new StrategyContext(new StrategyImpl1());
            context1.deal();
 			System.out.println("==================");
            StrategyContext context2 = new StrategyContext(new StrategyImpl2());
            context2.deal();
      }
}

结果:

StrategyImpl1
==================
StrategyImpl2

5、策略模式与spring集成实战

5.1 需求

与流程引擎组件对接,完成流程审批之后需要进行事件的回调,处理相关的业务逻辑。但是流程引擎传入的参数只有当前流程实例主键,表中有实例对应的模板类型,即属于哪个流程模板对应的实例。每个流程都有不同的模板,需要进行不同的设置。

5.2 实现

不同的流程模板枚举

package com.zengqingfa.examples.mybatisplus.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ProcessTypeEnum {
    CREATE_WAREHOUSE(1, "aa创建审批"), UPDATE_WAREHOUSE(2, "bb变更申请"), 
    DELETE_WAREHOUSE(3, "cc仓库申请"), OVERSOLD_ADJUST(4,"dd调整");
    private final Integer key;
    private final String desc;

    ProcessTypeEnum(int key, String desc) {
        this.key = key;
        this.desc = desc;
    }

    public static ProcessTypeEnum getEnum(Integer key) {
        ProcessTypeEnum[] processTypeEnums = values();
        for (ProcessTypeEnum processTypeEnum : processTypeEnums) {
            if (processTypeEnum.getKey().equals(key)) {
                return processTypeEnum;
            }
        }
        return null;
    }
}

策略接口

package com.zengqingfa.examples.mybatisplus.strategy;

import com.zengqingfa.examples.mybatisplus.enums.ProcessTypeEnum;

public interface ProcessStrategy {

    /**
     * 获取流程枚举
     * @return
     */
    ProcessTypeEnum getProcessTypeEnum();

    /**
     * 流程实例id
     * @param processId
     */
    String callBack(Long processId);
}

策略接口实现

实现1:

package com.zengqingfa.examples.mybatisplus.strategy.impl;

import com.zengqingfa.examples.mybatisplus.enums.ProcessTypeEnum;
import com.zengqingfa.examples.mybatisplus.strategy.ProcessStrategy;
import org.springframework.stereotype.Component;

@Component
public class CreateWarehouseStrategy implements ProcessStrategy {


    @Override
    public ProcessTypeEnum getProcessTypeEnum() {
        return  ProcessTypeEnum.CREATE_WAREHOUSE;
    }

    @Override
    public String callBack(Long processId) {
        System.out.println("CreateWarehouseStrategy....");
        return "CreateWarehouseStrategy";
    }
}

实现2:

package com.zengqingfa.examples.mybatisplus.strategy.impl;

import com.zengqingfa.examples.mybatisplus.enums.ProcessTypeEnum;
import com.zengqingfa.examples.mybatisplus.strategy.ProcessStrategy;
import org.springframework.stereotype.Component;

@Component
public class DeleteWarehouseStrategy implements ProcessStrategy {

    @Override
    public ProcessTypeEnum getProcessTypeEnum() {
        return ProcessTypeEnum.DELETE_WAREHOUSE;
    }

    @Override
    public String callBack(Long processId) {
        System.out.println("DeleteWarehouseStrategy....");
        return "DeleteWarehouseStrategy";
    }
}

策略工厂

根据流程实例id返回对应的策略

package com.zengqingfa.examples.mybatisplus.strategy;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.common.collect.Maps;
import com.zengqingfa.examples.mybatisplus.entity.WarehouseProcess;
import com.zengqingfa.examples.mybatisplus.enums.ProcessTypeEnum;
import com.zengqingfa.examples.mybatisplus.mapper.WarehouseProcessGenMapper;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@Component
public class ProcessStrategyFactory implements InitializingBean {

    @Autowired
    List<ProcessStrategy> processStrategies;

    private static Map<ProcessTypeEnum, ProcessStrategy> processStrategyMap = Maps.newHashMap();
    ;

    @Autowired
    private WarehouseProcessGenMapper mapper;

    @Override
    public void afterPropertiesSet() {
        //进行初始化
        if (CollectionUtils.isNotEmpty(processStrategies)) {
            processStrategies.stream().forEach(p -> {
                processStrategyMap.put(p.getProcessTypeEnum(), p);
            });
        }
    }

    /**
     * 通过流程实例id获取策略
     * @param processId
     * @return
     */
    public ProcessStrategy getByProcessId(Long processId) {
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.eq(WarehouseProcess.PROCESS_ID, processId);
        WarehouseProcess warehouseProcess = mapper.selectOne(queryWrapper);
        if(warehouseProcess==null){
            return null;
        }
        ProcessTypeEnum anEnum = ProcessTypeEnum.getEnum(warehouseProcess.getProcessType());
        if (anEnum == null) {
            return null;
        }
        return processStrategyMap.get(anEnum);
    }
}

流程实例上下文

@Component
public class ProcessStrategyContext {

    @Autowired
    ProcessStrategyFactory processStrategyFactory;

    public String process(Long processId) {
        ProcessStrategy processStrategy = processStrategyFactory.getByProcessId(processId);
        if (processStrategy == null) {
            throw new IllegalArgumentException("流程类型没有对应的策略,processId=" + processId);
        }
        return processStrategy.callBack(processId);
    }
}

测试

@RestController
@RequestMapping("/process")
public class ProcessController {

    @Autowired
    ProcessStrategyContext processStrategyContext;

    @GetMapping("/getStrategy")
    public Result selectById(@RequestParam("processId") Long processId) {
        String process = processStrategyContext.process(processId);
        return Result.success(process);
    }
}

前端根据不同的流程实例id返回不同的策略,执行不同的逻辑

6、策略模式总结

6.1 策略模式的主要优点

  • 策略模式提供了对 “开闭原则” 的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  • 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。
  • 策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式而是通过继承,这样算法的使用就
    和算法本身混在一起,不符合 “单一职责原则”,而且使用继承无法实现算法或行为在程序运行时的动态切
    换。
  • 使用策略模式可以避免多重条件选择语句。多重条件选择语句是硬编码,不易维护。
  • 策略模式提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类。

6.2 策略模式的主要缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
  • 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
  • 无法同时在客户端使用多个策略类,也就是说,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能的情况。

6.3 适用场景

  • 一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法类中,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,根据 “里氏代换原则” 和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需要维持一个数据类型是抽象算法类的对象。

  • 一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。

  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。

7、源码中的策略模式

7.1 Comparator 中的策略模式

在Arrays类中有一个 sort() 方法,如下:

public class Arrays{
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
}

Arrays就是一个环境角色类,这个sort方法可以传一个新策略让Arrays根据这个策略来进行排序。就比如下面的测试类。
测试

package com.zengqingfa.designpattern.strategy;

import java.util.Arrays;
import java.util.Comparator;

public class Client {

    public static void main(String[] args) {
        Integer[] intArray = {89, 3, 67, 12, 45};
        // 匿名内部类无需专门定义形态完整的类,只需指明新创建的实例从哪个接口扩展而来
        Arrays.sort(intArray, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2); // 倒过来的参数顺序变成了降序
            }
        });
        String descDesc = "intArray采取匿名内部类的降序结果为:";
        for (Integer item : intArray) {
            descDesc = descDesc + item + ", ";
        }
        //3, 12, 45, 67, 89,
        System.out.println(descDesc);
        //降序
        Arrays.sort(intArray, (o1, o2) -> Integer.compare(o2, o1));
        String descDesc2 = "intArray采取匿名内部类的降序结果为:";
        for (Integer item : intArray) {
            descDesc2 = descDesc2 + item + ", ";
        }
        //89, 67, 45, 12, 3, 
        System.out.println(descDesc2);
    }
}

这里我们在调用Arrays的sort方法时,第二个参数传递的是Comparator接口的子实现类对象。所以Comparator充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色。环境角色类(Arrays)应该持有抽象策略的引用来调用。那么,Arrays类的sort方法到底有没有使用Comparator子实现类中的 compare() 方法吗?让我们继续查看TimSort类的 sort() 方法,代码如下:

class TimSort<T> {
    static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
                         T[] work, int workBase, int workLen) {
        assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
            binarySort(a, lo, hi, lo + initRunLen, c);
            return;
        }
        ...
    }   
        
    private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) {
        assert lo < hi;
        int runHi = lo + 1;
        if (runHi == hi)
            return 1;

        // Find end of run, and reverse range if descending
        if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
                runHi++;
            reverseRange(a, lo, runHi);
        } else {                              // Ascending
            while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
                runHi++;
        }

        return runHi - lo;
    }
}

上面的代码中最终会跑到 countRunAndMakeAscending() 这个方法中。我们可以看见,只用了compare方法,所以在调用Arrays.sort方法只传具体compare重写方法的类对象就行,这也是Comparator接口中必须要子类实现的一个方法。

7.2 ThreadPoolExecutor中的四种拒绝策略

策略接口

public interface RejectedExecutionHandler {

    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

四种拒绝策略实现
基于静态内部类实现

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

    /**
     * A handler for rejected tasks that throws a
     * {@code RejectedExecutionException}.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

    /**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

    /**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries {@code execute}, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

策略使用
java.util.concurrent.ThreadPoolExecutor构造器
基于传入的策略使用不同的拒绝策略

    private volatile RejectedExecutionHandler handler;    

	public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }

7.3 spring中使用不同的策略来初始化bean

InstantiationStrategy 负责使用 Bean 类的默认构造函数、带参构造函数或者工厂方法等来实例化 Bean,是一个策略模式的接口。
org.springframework.beans.factory.support.InstantiationStrategy
策略接口

public interface InstantiationStrategy {


	Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner)
			throws BeansException;


	Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			Constructor<?> ctor, Object... args) throws BeansException;


	Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			@Nullable Object factoryBean, Method factoryMethod, Object... args)
			throws BeansException;

}

实现类
org.springframework.beans.factory.support.SimpleInstantiationStrategy

	protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		throw new UnsupportedOperationException("Method Injection not supported in SimpleInstantiationStrategy");
	}

	@Override
	public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			final Constructor<?> ctor, Object... args) {

		if (!bd.hasMethodOverrides()) {
			if (System.getSecurityManager() != null) {
				// use own privileged to change accessibility (when security is on)
				AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
					ReflectionUtils.makeAccessible(ctor);
					return null;
				});
			}
			return BeanUtils.instantiateClass(ctor, args);
		}
		else {
			return instantiateWithMethodInjection(bd, beanName, owner, ctor, args);
		}
	}

	/**
	 * Subclasses can override this method, which is implemented to throw
	 * UnsupportedOperationException, if they can instantiate an object with
	 * the Method Injection specified in the given RootBeanDefinition.
	 * Instantiation should use the given constructor and parameters.
	 */
	protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName,
			BeanFactory owner, @Nullable Constructor<?> ctor, Object... args) {

		throw new UnsupportedOperationException("Method Injection not supported in SimpleInstantiationStrategy");
	}

org.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy

	@Override
	protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		return instantiateWithMethodInjection(bd, beanName, owner, null);
	}

	@Override
	protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
			@Nullable Constructor<?> ctor, Object... args) {

		// Must generate CGLIB subclass...
		return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
	}

SimpleInstantiationStrategy 类实现了 InstantiationStrategy 接口,是一个简单用于实例化 Bean 的类。该类有一个 instantiationWithMethodInjection 方法,但是实际上这只是个钩子,并非真正支持方法注入功能。

是个钩子,并非真正支持方法注入功能。

真正支持方法注入功能的是 CglibSubclassingInstantiationStrategy 类,它继承了 SimpleInstantiationStrategy 并覆盖了 instantiationWithMethodInjection 方法。不过使用这个方法必须用到 cglib 类库,它利用 cglib 为 bean 动态生成子类,也就是代理类,在子类中生成方法注入的逻辑,然后使用这个动态生成的子类创建 bean 的实例。
image.png