设计模式 | 策略模式

267 阅读4分钟

概述

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

策略模式(Strategy Design Pattern)定义了一系列算法,将每个算法分别封装起来,并使它们可以相互替换。

策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。 策略模式只适用管理一组同类型的算法,并且这些算法是完全互斥的情况。

实现

策略模式-UML

  • Strategy(抽象策略):特定策略的抽象
  • ConcreteStrategy(具体策略):实现抽象策略的类
  • Context(环境):运行特定策略的类

策略模式的通用写法:

func main() {
	// 选择一个具体策略
	strategy := new(concreteStrategyA)
	// 创建一个上下文环境
	context := NewContext(strategy)
	// 客户端直接让上下文角色执行算法
	context.do()
}

// IStrategy 抽象策略
type IStrategy interface {
	algorithm()
}

// concreteStrategyA 具体策略
type concreteStrategyA struct{}

func (*concreteStrategyA) algorithm() {
	fmt.Println("Strategy A")
}

// concreteStrategyB 具体策略
type concreteStrategyB struct{}

func (*concreteStrategyB) algorithm() {
	fmt.Println("Strategy B")
}

// Context 上下文环境
type Context struct {
	strategy IStrategy
}

func NewContext(strategy IStrategy) *Context {
	return &Context{
		strategy: strategy,
	}
}

// 调用策略中的方法
func (context *Context) do() {
	context.strategy.algorithm()
}

应用

策略模式在JDK中的应用:

// 处理不能被 ThreadPoolExecutor 执行的 tasks
// 该策略接口有四个实现类
public interface RejectedExecutionHandler {

    /**
     *
     * Method that may be invoked by a {@link ThreadPoolExecutor} when
     *
     * {@link ThreadPoolExecutor#execute execute} cannot accept a
     *
     * task. This may occur when no more threads or queue slots are
     * available because their bounds would be exceeded, or upon
     * shutdown of the Executor.
     *
     * <p>
     * In the absence of other alternatives, the method may throw
     *
     * an unchecked {@link RejectedExecutionException}, which will be
     *
     * propagated to the caller of {@code execute}.
     *
     *
     * @param r        the runnable task requested to be executed
     *
     * @param executor the executor attempting to execute this task
     *
     * @throws RejectedExecutionException if there is no remedy
     *
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

/* Predefined RejectedExecutionHandlers */

/**
 *
 * A handler for rejected tasks that runs the rejected task
 * directly in the calling thread of the {@code execute} method,
 *
 * unless the executor has been shut down, in which case the task
 * is discarded.
 */
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
 * {@link RejectedExecutionException}.
 *
 *
 * This is the default handler for {@link ThreadPoolExecutor} and
 *
 * {@link ScheduledThreadPoolExecutor}.
 *
 */
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. This policy is
 * rarely useful in cases where other threads may be waiting for
 * tasks to terminate, or failures must be recorded. Instead consider
 * using a handler of the form:
 *
 * <pre> {@code
 *
 * new RejectedExecutionHandler() {
 *
 *     public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
 *         Runnable dropped = e.getQueue().poll();
 *         if (dropped instanceof Future<?>) {
 *
 *             ((Future<?>) dropped).cancel(false);
 *
 *             // also consider logging the failure
 *         }
 *         e.execute(r); // retry
 *     }
 * }
 * }</pre>
 *
 */

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);
        }
    }
}

总结

策略模式的优点:

  • 策略模式符合开闭原则
  • 避免使用多重条件转移语句,如if...else语句、switch...case预警
  • 使用策略模式可以提高算法的保密性和安全行

策略模式的缺点:

  • 客户端必须知道所有的策略,并且自行决定使用哪一个策略类;在实际项目中,一般通过工厂方法模式来实现策略类的声明
  • 代码中会产生非常多策略类,增加维护难度

使用策略工厂模式来管理策略类:

func main() {
	factory := NewStrategyFactory()
	if strategy, ok := factory.GetStrategy("B"); ok {
		strategy.algorithm()
	}
}

// 策略工厂
type StrategyFactory struct {
	strategys map[string]IStrategy
}

func NewStrategyFactory() *StrategyFactory {
	factory := new(StrategyFactory)
	strategys := map[string]IStrategy{
		"A": new(concreteStrategyA),
		"B": new(concreteStrategyB),
	}

	factory.strategys = strategys
	return factory
}

func (factory *StrategyFactory) GetStrategy(name string) (strategy IStrategy, ok bool) {
	if v, ok := factory.strategys[name]; ok {
		return v, true
	}
	return nil, false
}

我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿