设计模式之策略模式

826 阅读5分钟

使用场景

策略模式是我们常用的一种行为设计模式。简单来讲,在系统中,针对多种类型的判断,策略模式可以帮助我们规避if...else的判断,并能够针对不同的类型动态选择不同实现,提供高扩展性。

举个例子:美女有很多种,比如说有御姐型,萝莉型,气质型,那么对于你来说,针对每一种类型的妹子,表白的方法肯定不一样。比如现在喜欢的是御姐型,你就可以写一个接口,定义一个向御姐表白的方法,那么将来你不只喜欢上御姐,还喜欢萝莉,那这个时候你怎么去扩展,你还需要告诉别人,现在你不只喜欢御姐还喜欢萝莉,需要在代码里面加if else判断对方是御姐还是萝莉,去选择对应的表白方法,这个时候使用策略模式就可以达到自动判断对方是御姐型还是萝莉型,并自动选择对应的表白方法

策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法,每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

实际运用

直接来看一个实际运用的例子。假设有一个需求,需要对GitLab的事件进行监听,如下图可以看到事件的类型有多种。

image.png 类似这种,假设我们在一个接口中,要处理多种策略,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为

  • 1、定义一个策略接口
public interface GitEventStrategy  {
    /**
     *  监听处理方法
     * @param jsonObject 监听报文
     * @return
     */
     void handleEvent(JSONObject jsonObject) throws Exception;
}
  • 2、定义一个策略环境类,根据报文中的不同事件类型,选择不同的实现。这里将beanName与事件的类型做了映射,具体的策略实现选择取决于jsonObject.getString("object_kind")的值。
@Component
public class GitEventStrategyContext {
    @Autowired
    private Map<String, GitEventStrategy> strategyMap;

    public void handle(JSONObject jsonObject) throws Exception {
        if (strategyMap != null) {
            GitEventStrategy eventStrategy = strategyMap.get(jsonObject.getString("object_kind"));
            if (eventStrategy == null) {
                return;
            }
            eventStrategy.handleEvent(jsonObject);
        }
    }

}
  • 3、定义具体的策略实现
@Service("merge_request")
public class GitMergeImpl implements GitEventStrategy {

    @Override
    public void handleEvent(JSONObject jsonObject) throws Exception {
      ......
    }

}
  • 4、客户端调用
@RequestMapping("/gitEvent")
public class GitEventController {
    @Autowired
    GitEventStrategyContext gitEventStrategyContext;

    @ApiOperation(httpMethod = "POST", value = "事件监听处理")
    @RequestMapping("handle")
    public MessageBean handle(@RequestBody JSONObject jsonObject, HttpServletRequest request) throws Exception {
        gitEventStrategyContext.handle(jsonObject);
        return MessageBean.success();
    }

}

这里只实现了merge_request事件,如果要扩展实现其它的事件,只需要新增一个具体事件的实现类即可。是不是很方便?

可以看到在上面的例子中,不同的事件可以动态的切换,同时避免了使用if...else对不同的事件进行判断,扩展不同事件的也非常简单,这就是策略模式的好处

结构

  在策略模式,有以下3中角色

  • 抽象策略(Strategy)角色: 通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口,如上文的GitEventStrategy接口
  • 环境(Context)角色: 持有Strategy的引用,如上文的GitEventStrategyContext
  • 具体策略(ConcreteStrategy)角色: 包装了相关的算法或行为,如上文的GitMergeImpl

策略模式在ThreadPoolExecutor中的运用

你知道如果线程池的队列满了之后,会发生什么事情吗?
策略模式在jdk源码中也有大量的运用,本章介绍在通过策略模式实现ThreadPoolExecutor的拒绝策略

抽象策略类

public interface RejectedExecutionHandler {

    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

通过ThreadPoolExecutor的构造方法可以选择不同的RejectedExecutionHandler作为拒绝策略的实现

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

当一个任务通过execute(Runnable)方法欲添加到线程池时,核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,则使用对应的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);
    }

具体的4种不同实现如下:

  • 调用ThreadPoolExecutor.execute方法的线程中运行被拒绝的任务,除非执行程序已经关闭,在这种情况下此任务被丢弃
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
  
        public CallerRunsPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }
  • 直接抛出java.util.concurrent.RejectedExecutionException异常
    public static class AbortPolicy implements RejectedExecutionHandler {
    
       public AbortPolicy() { }

       public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           throw new RejectedExecutionException("Task " + r.toString() +
                                                " rejected from " +
                                                e.toString());
       }
   }
  • 丢弃策略。无法执行的任务将被简单地删除,与策略1不同,不抛出异常
    public static class DiscardPolicy implements RejectedExecutionHandler {
    
       public DiscardPolicy() { }

       public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
       }
   }
  • 在任务队列头的任务会被丢弃,然后重试执行
 public static class DiscardOldestPolicy implements RejectedExecutionHandler {
   
       public DiscardOldestPolicy() { }

       public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           if (!e.isShutdown()) {
               e.getQueue().poll();
               e.execute(r);
           }
       }
   }

这样写的好处是什么呢?除了可以根据需求动态的设置需要的拒绝策略,还可以很简单的对拒绝策略进行扩展,只要实现RejectedExecutionHandler接口并重写其方法,就能够对拒绝策略进行扩展。