【设计模式】策略模式(Strategy Pattern)

2,160 阅读7分钟

前言

本文将阐述设计模式中的策略模式,包括策略模式的应用场景、框架源码分析等,最后综合阐述下策略模式的优缺点。

希望可以帮忙大家更好的理解策略模式。@空歌白石

策略模式定义

策略模式(Strategy Pattern)又被称之为政策模式(Policy Pattern),它是将定义的算法家族、分别封装起来,让它们之间可以相互替换,从而让算法的变化不会影响到使用算法的用户。可以最大程度的避免if else以及switch语句。

策略模式属于行为型模式。

应用场景

线上线下支付方式选择

在线上线下支付时,可以选择支付宝、微信支付、云闪付,每种方式的选择都能够完成付款的行为,并且只能选择一种支付方法完成。

支付方式.jpeg

出行方式

假设我需要从上海到石家庄,有哪些出行方式可以选择呢?我们可以选择飞机、高铁、动车、普速列车、驾车、大巴等等多种方式。但是不管通过哪种方式,都可以从上海到达石家庄,并且在同一时间只有一种交通方式可选择。

出行方式.jpeg

个人所得税计算

个人所得税的计算分了不同的阶梯,每个阶梯计算的税率不同,每个收入金额中只有一个所得税税率。

个人所得税.jpeg

总结

策略模式主要包含以下几种场景:

  1. 假设系统中有很多类,而他们的区别仅仅在于他们的行为不同。
  2. 一个系统需要动态的在几种算法中选择其中的一种。
  3. 需要屏蔽算法规则。避免if else

策略模式的实现

我们首先看下策略模式的类图。

DragonSlayingStrategy.png

定义统一行为抽象

Strategy的接口负责定义有哪些行为需要执行。

public interface DragonSlayingStrategy {

  void execute();

}

定义具体行为执行者

以下我们定义了三种不同的执行策略,分别为MeleeProjecttileSpell

public class MeleeStrategy implements DragonSlayingStrategy {

  @Override
  public void execute() {
    LOGGER.info("With your Excalibur you sever the dragon's head!");
  }
}
public class ProjectileStrategy implements DragonSlayingStrategy {

  @Override
  public void execute() {
    LOGGER.info("You shoot the dragon with the magical crossbow and it falls dead on the ground!");
  }
}
public class SpellStrategy implements DragonSlayingStrategy {

  @Override
  public void execute() {
    LOGGER.info("You cast the spell of disintegration and the dragon vaporizes in a pile of dust!");
  }

}

策略调度者

上文中定义了策略抽象以及具体的策略行为,接下来就是如何使用这些策略了。这部分分为两种实现,第二种使用工厂的方式封装的更优雅些。

封装类实现

通过定义一个DragonSlayer类,来封装对具体策略和行的统一执行。

public class DragonSlayer {

  private DragonSlayingStrategy strategy;

  public DragonSlayer(DragonSlayingStrategy strategy) {
    this.strategy = strategy;
  }

  public void changeStrategy(DragonSlayingStrategy strategy) {
    this.strategy = strategy;
  }

  public void goToBattle() {
    strategy.execute();
  }
}

具体的执行方式。

DragonSlayer dragonSlayer = new DragonSlayer(new MeleeStrategy());
dragonSlayer.goToBattle();


dragonSlayer.changeStrategy(new ProjectileStrategy());
dragonSlayer.goToBattle();


dragonSlayer.changeStrategy(new SpellStrategy());
dragonSlayer.goToBattle();

可以看出以上的执行中,需要由调用方在具体执行时选择使用哪种具体的策略。那么可以进行优化吗?可以使用工厂方法进一步的封装。

工厂方法实现

使用简单的工厂方法,实现策略的调度者。

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class DragonSlayerFactory {
    private static Map<String, DragonSlayingStrategy> map = new HashMap<>();

    static {
        map.put(StrategyKey.MELEE, new MeleeStrategy());
        map.put(StrategyKey.PROJECTILE, new ProjectileStrategy());
        map.put(StrategyKey.SPELL, new SpellStrategy());
    }

    private DragonSlayerFactory() {
    }

    public static DragonSlayingStrategy getDragonSlayingStrategy(String strategyKey) {
        if (!map.containsKey(strategyKey)) {
            return null;
        }
        return map.get(strategyKey);
    }

    public static Set<String> getAllStrategy() {
        return map.keySet();
    }

    private interface StrategyKey {
        String MELEE = "melee";
        String PROJECTILE = "Projectile";
        String SPELL = "Spell";
    }
}

框架源码分析

JDK中的Comparator

Comparator定义了compare方法。

    /**
     * Compares its two arguments for order.  Returns a negative integer,
     * zero, or a positive integer as the first argument is less than, equal
     * to, or greater than the second.<p>
     *
     * The implementor must ensure that {@link Integer#signum
     * signum}{@code (compare(x, y)) == -signum(compare(y, x))} for
     * all {@code x} and {@code y}.  (This implies that {@code
     * compare(x, y)} must throw an exception if and only if {@code
     * compare(y, x)} throws an exception.)<p>
     *
     * The implementor must also ensure that the relation is transitive:
     * {@code ((compare(x, y)>0) && (compare(y, z)>0))} implies
     * {@code compare(x, z)>0}.<p>
     *
     * Finally, the implementor must ensure that {@code compare(x,
     * y)==0} implies that {@code signum(compare(x,
     * z))==signum(compare(y, z))} for all {@code z}.
     *
     * @apiNote
     * It is generally the case, but <i>not</i> strictly required that
     * {@code (compare(x, y)==0) == (x.equals(y))}.  Generally speaking,
     * any comparator that violates this condition should clearly indicate
     * this fact.  The recommended language is "Note: this comparator
     * imposes orderings that are inconsistent with equals."
     *
     * @param o1 the first object to be compared.
     * @param o2 the second object to be compared.
     * @return a negative integer, zero, or a positive integer as the
     *         first argument is less than, equal to, or greater than the
     *         second.
     * @throws NullPointerException if an argument is null and this
     *         comparator does not permit null arguments
     * @throws ClassCastException if the arguments' types prevent them from
     *         being compared by this comparator.
     */
    int compare(T o1, T o2);

Arrays类的compare方法传入了Comparator策略,可以由使用方具体执行具体的比较器。

public static <T> int compare(T[] a, T[] b,
                                Comparator<? super T> cmp) {
    Objects.requireNonNull(cmp);
    if (a == b)
        return 0;
    if (a == null || b == null)
        return a == null ? -1 : 1;

    int length = Math.min(a.length, b.length);
    for (int i = 0; i < length; i++) {
        T oa = a[i];
        T ob = b[i];
        if (oa != ob) {
            // Null-value comparison is deferred to the comparator
            int v = cmp.compare(oa, ob);
            if (v != 0) {
                return v;
            }
        }
    }

    return a.length - b.length;
}

Spring InstantiationStrategy

InstantiationStrategy负责Spring Bean初始化的策略。

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.lang.Nullable;

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, @Nullable Object... args) throws BeansException;

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

}

InstantiationStrategy分为两种实现,分别为SimpleInstantiationStrategyCglibSubclassingInstantiationStrategy。但有Cglib时,使用CglibSubclassingInstantiationStrategy,否则使用默认的SimpleInstantiationStrategy策略。

public class SimpleInstantiationStrategy implements InstantiationStrategy {

	private static final ThreadLocal<Method> currentlyInvokedFactoryMethod = new ThreadLocal<>();


	/**
	 * Return the factory method currently being invoked or {@code null} if none.
	 * <p>Allows factory method implementations to determine whether the current
	 * caller is the container itself as opposed to user code.
	 */
	@Nullable
	public static Method getCurrentlyInvokedFactoryMethod() {
		return currentlyInvokedFactoryMethod.get();
	}


	@Override
	public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) {
		// Don't override the class with CGLIB if no overrides.
		if (!bd.hasMethodOverrides()) {
			Constructor<?> constructorToUse;
			synchronized (bd.constructorArgumentLock) {
				constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
				if (constructorToUse == null) {
					final Class<?> clazz = bd.getBeanClass();
					if (clazz.isInterface()) {
						throw new BeanInstantiationException(clazz, "Specified class is an interface");
					}
					try {
						if (System.getSecurityManager() != null) {
							constructorToUse = AccessController.doPrivileged(
									(PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
						}
						else {
							constructorToUse = clazz.getDeclaredConstructor();
						}
						bd.resolvedConstructorOrFactoryMethod = constructorToUse;
					}
					catch (Throwable ex) {
						throw new BeanInstantiationException(clazz, "No default constructor found", ex);
					}
				}
			}
			return BeanUtils.instantiateClass(constructorToUse);
		}
		else {
			// Must generate CGLIB subclass.
			return instantiateWithMethodInjection(bd, beanName, owner);
		}
	}
    // 空歌白石:省略剩余代码
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy {

	/**
	 * Index in the CGLIB callback array for passthrough behavior,
	 * in which case the subclass won't override the original class.
	 */
	private static final int PASSTHROUGH = 0;

	/**
	 * Index in the CGLIB callback array for a method that should
	 * be overridden to provide <em>method lookup</em>.
	 */
	private static final int LOOKUP_OVERRIDE = 1;

	/**
	 * Index in the CGLIB callback array for a method that should
	 * be overridden using generic <em>method replacer</em> functionality.
	 */
	private static final int METHOD_REPLACER = 2;


	@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, @Nullable Object... args) {

		// Must generate CGLIB subclass...
		return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
	}
    // 空歌白石:省略剩余代码

Guava Splitter Strategy

处理字符串时经常存在拆分的场景,GuavaSplitter就是其中一个很好的工具类。在Splitter中定义了一个Strategy接口。

private interface Strategy {
    Iterator<String> iterator(Splitter splitter, CharSequence toSplit);
}

Splitter的私有构造方法中需要传入具体的Strategy

private final Strategy strategy;
private final int limit;

private Splitter(Strategy strategy) {
    this(strategy, false, CharMatcher.none(), Integer.MAX_VALUE);
}

fixedLengthon方法中具体实现了iterator策略。

public static Splitter fixedLength(final int length) {
    checkArgument(length > 0, "The length may not be less than 1");

    return new Splitter(
        new Strategy() {
            @Override
            public SplittingIterator iterator(final Splitter splitter, CharSequence toSplit) {
                return new SplittingIterator(splitter, toSplit) {
                    @Override
                    public int separatorStart(int start) {
                        int nextChunkStart = start + length;
                        return (nextChunkStart < toSplit.length() ? nextChunkStart : -1);
                    }

                    @Override
                    public int separatorEnd(int separatorPosition) {
                        return separatorPosition;
                    }
                };
            }
        });
}
public static Splitter on(final String separator) {
    checkArgument(separator.length() != 0, "The separator may not be the empty string.");
    if (separator.length() == 1) {
        return Splitter.on(separator.charAt(0));
    }
    return new Splitter(
        new Strategy() {
            @Override
            public SplittingIterator iterator(Splitter splitter, CharSequence toSplit) {
            return new SplittingIterator(splitter, toSplit) {
                @Override
                public int separatorStart(int start) {
                    int separatorLength = separator.length();

                    positions:
                    for (int p = start, last = toSplit.length() - separatorLength; p <= last; p++) {
                        for (int i = 0; i < separatorLength; i++) {
                            if (toSplit.charAt(i + p) != separator.charAt(i)) {
                                continue positions;
                            }
                        }
                        return p;
                    }
                    return -1;
                }

                @Override
                public int separatorEnd(int separatorPosition) {
                    return separatorPosition + separator.length();
                }
            };
            }
        });
}
private static Splitter on(final CommonPattern separatorPattern) {
    checkArgument(
        !separatorPattern.matcher("").matches(),
        "The pattern may not match the empty string: %s",
        separatorPattern);

    return new Splitter(
        new Strategy() {
            @Override
            public SplittingIterator iterator(final Splitter splitter, CharSequence toSplit) {
                final CommonMatcher matcher = separatorPattern.matcher(toSplit);
                return new SplittingIterator(splitter, toSplit) {
                    @Override
                    public int separatorStart(int start) {
                        return matcher.find(start) ? matcher.start() : -1;
                    }

                    @Override
                    public int separatorEnd(int separatorPosition) {
                        return matcher.end();
                    }
                };
            }
        });
}

优缺点

优点

  1. 策略模式符合开闭原则,便于维护扩展。
  2. 避免使用多重条件转移语句
    1. if elseswtich等,避免了臃肿的条件语句
  3. 使用策略模式可以提高算法的保密性和安全性
    1. 可以把方法进行更加合理的封装。

缺点

  1. 客户端必须知道所有的策略,并且自行决定使用哪一种策略。
  2. 代码中会产生非常多的策略类,增加策略的维护成本。

结束语

设计模式可以使得代码更加优雅,增强代码的扩展性。但是万万不能为了设计模式而设计模式,如果真的这样做了,反而会适得其反,画蛇添足,大幅度增加代码复杂度和降低可维护性。只有在充分的分析业务场景、代码结构的前提下合理的使用设计模式,才能发挥出设计模式最大的作用。

最后一句:设计模式是道法,并不是术法。理解内涵最为重要。

192e0e94cf70d76b0fafc55b59fa1ad2.jpeg