行为型-策略模式


一、介绍

策略模式(Strategy):针对一组算法,将每一个算法封装到具有共同接口的独立的类中,使得它们可以互换。

使用策略模式可以把行为和环境分割开来。

本质:分离算法,选择实现。

策略模式有下面几个部分:

  • 环境(Context):有一个Strategy类的引用,和具体的策略类交互。

  • 抽象策略(Strategy)角色:一个接口或抽象类,给出规范。

  • 具体策略(ConcreteStrategy)角色:具体算法或行为。

二、代码

首先,先定义一个策略接口:

public interface Strategy {
   public void draw(int radius, int x, int y);
}

然后我们定义具体的几个策略:

public class RedPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用红色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class GreenPen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用绿色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}
public class BluePen implements Strategy {
   @Override
   public void draw(int radius, int x, int y) {
      System.out.println("用蓝色笔画图,radius:" + radius + ", x:" + x + ", y:" + y);
   }
}

使用策略的类:

public class Context {
   private Strategy strategy;

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

   public int executeDraw(int radius, int x, int y){
      return strategy.draw(radius, x, y);
   }
}

客户端演示:

public static void main(String[] args) {
    Context context = new Context(new BluePen()); // 使用绿色笔来画
      context.executeDraw(10, 0, 0);
}

三、源码

1、ThreadPoolExecutor

创建线程池时,会调用 ThreadPoolExecutor 的构造函数 new 一个对象,在构造函数中需要传入七个参数,其中有一个参数叫 RejectedExecutionHandler handler 也就是线程的拒绝策略。

public class ThreadPoolExecutor extends AbstractExecutorService {

    // 成员变量
    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;
    }

    // 拒绝任务
    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }
}

传入拒绝策略之后将对象赋给 ThreadPoolExecutor 对象的成员变量 handler,在需要对加入线程池的线程进行拒绝时,直接调用 RejectedExecutionHandler 中的 reject 方法即可,方法内部调用传入 handler 的 rejectedExecution 方法。

但是 RejectedExecutionHandler 是一个接口,也就是说我们需要传入具体的实现,这里便是使用的策略模式。RejectedExecutionHandler 接口对应 Strategy 接口,下面四种实现类对应具体策略;RejectedExecutionHandler 对应 Context 类,外部调用 RejectedExecutionHandler 的 reject 方法,再由 RejectedExecutionHandler 内部调用具体策略实现的方法。

public interface RejectedExecutionHandler {

    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

2、Spring MVC中 DispatcherServlet

DispatcherServlet 在进行转发前需要进行传说中的九大件的初始化,其中去初始化时除了 initMultipartResolver(上传文件)没有获取 Properties defaultStrategies;默认策略,其他的八大件都会使用到策略模式。先看一下 defaultStrategiesjava.util.Properties 类型,其实就是个 Map,定义如下:

public class Properties extends Hashtable<Object,Object> {
    // ... 
}

流程梳理:

1、当Web容器启动时,ServletWebServerApplicationContext 初始化会调用其refresh()方法,则会调用 DispatcherServletonRefresh 方法

2、onRefresh 方法 - > initStrategies 方法 -> 初始化九大件

3、初始化时则会调用 getDefaultStrategy 方法。

    protected void initStrategies(ApplicationContext context) {
        this.initMultipartResolver(context);
        this.initLocaleResolver(context);
        this.initThemeResolver(context);
        this.initHandlerMappings(context);
        this.initHandlerAdapters(context);
        this.initHandlerExceptionResolvers(context);
        this.initRequestToViewNameTranslator(context);
        this.initViewResolvers(context);
        this.initFlashMapManager(context);
    }

这几个方法的实际基本都是差不多的,这边以 initLocaleResolver 为例

private void initLocaleResolver(ApplicationContext context) {
    try {
        this.localeResolver = (LocaleResolver)context.getBean("localeResolver", LocaleResolver.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("Detected " + this.localeResolver);
        } else if (this.logger.isDebugEnabled()) {
            this.logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
        }
    } catch (NoSuchBeanDefinitionException var3) {
        this.localeResolver = (LocaleResolver)this.getDefaultStrategy(context, LocaleResolver.class);
        if (this.logger.isTraceEnabled()) {
            this.logger.trace("No LocaleResolver 'localeResolver': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
        }
    }

}

进入 getDefaultStrategy 方法

protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
    List<T> strategies = this.getDefaultStrategies(context, strategyInterface);
    if (strategies.size() != 1) {
        throw new BeanInitializationException("DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
    } else {
        return strategies.get(0);
    }
}

再往下

private static final Properties defaultStrategies;

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
    String key = strategyInterface.getName();

    // 从defaultStrategies获取指定的值
    String value = defaultStrategies.getProperty(key);

    if (value == null) {
        return new LinkedList();
    } else {
        String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
        List<T> strategies = new ArrayList(classNames.length);
        String[] var7 = classNames;
        int var8 = classNames.length;

        for(int var9 = 0; var9 < var8; ++var9) {
            String className = var7[var9];

            try {
                Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
                Object strategy = this.createDefaultStrategy(context, clazz);
                strategies.add(strategy);
            } catch (ClassNotFoundException var13) {
                throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", var13);
            } catch (LinkageError var14) {
                throw new BeanInitializationException("Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", var14);
            }
        }

        return strategies;
    }
}

小结: web容器启动,ServletWebServerApplicationContext 的refresh方法 间接调用到 DispatcherServlet的初始九大件方法, 其中八大件在没有自定义实现的情况下,调用默认的 配置。 而默认配置则是在 DispatcherServlet的静态代码块中,由Spring的ClassPathResource将配置文件DispatcherServlet.properties中的配置加载进一个 Map容器中。只待初始化九大件时,根据不同的九大件类型作为key,调用相应的实现。

四、SpringBoot中使用策略模式

1、接口

public interface PayResultService {

    public static final String BEAN_NAME_PREFIX = "payResultService_";

    PayCallbackVO dealPayResult(CommandPayDTO commandPayDTO);
}

2、实现类

/**
 * 支付成功
 */
@Service(PayResultService.BEAN_NAME_PREFIX + PayStatus.PAY_SUCCEED_STATUS)
public class PayResultSucceedService implements PayResultService {

}
/**
 * 支付失败
 */
@Service(PayResultService.BEAN_NAME_PREFIX + PayStatus.PAY_FAIL_STATUS)
public class PayResultFailService implements PayResultService {

}

3、使用

    @Autowired
    private Map<String, PayResultService> payResultServiceMap;

  目录