前言
本文将阐述设计模式中的策略模式,包括策略模式的应用场景、框架源码分析等,最后综合阐述下策略模式的优缺点。
希望可以帮忙大家更好的理解策略模式。@空歌白石
策略模式定义
策略模式(Strategy Pattern)又被称之为政策模式(Policy Pattern),它是将定义的算法家族、分别封装起来,让它们之间可以相互替换,从而让算法的变化不会影响到使用算法的用户。可以最大程度的避免if else以及switch语句。
策略模式属于行为型模式。
应用场景
线上线下支付方式选择
在线上线下支付时,可以选择支付宝、微信支付、云闪付,每种方式的选择都能够完成付款的行为,并且只能选择一种支付方法完成。
出行方式
假设我需要从上海到石家庄,有哪些出行方式可以选择呢?我们可以选择飞机、高铁、动车、普速列车、驾车、大巴等等多种方式。但是不管通过哪种方式,都可以从上海到达石家庄,并且在同一时间只有一种交通方式可选择。
个人所得税计算
个人所得税的计算分了不同的阶梯,每个阶梯计算的税率不同,每个收入金额中只有一个所得税税率。
总结
策略模式主要包含以下几种场景:
- 假设系统中有很多类,而他们的区别仅仅在于他们的
行为不同。 - 一个系统需要动态的在几种算法中选择其中的一种。
- 需要屏蔽算法规则。避免
if else
策略模式的实现
我们首先看下策略模式的类图。
定义统一行为抽象
Strategy的接口负责定义有哪些行为需要执行。
public interface DragonSlayingStrategy {
void execute();
}
定义具体行为执行者
以下我们定义了三种不同的执行策略,分别为Melee,Projecttile,Spell。
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分为两种实现,分别为SimpleInstantiationStrategy,CglibSubclassingInstantiationStrategy。但有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
处理字符串时经常存在拆分的场景,Guava的Splitter就是其中一个很好的工具类。在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);
}
fixedLength和on方法中具体实现了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();
}
};
}
});
}
优缺点
优点
- 策略模式符合开闭原则,便于维护扩展。
- 避免使用多重条件转移语句
- 如
if else,swtich等,避免了臃肿的条件语句
- 如
- 使用策略模式可以提高算法的保密性和安全性
- 可以把方法进行更加合理的封装。
缺点
- 客户端必须知道所有的策略,并且自行决定使用哪一种策略。
- 代码中会产生非常多的策略类,增加策略的维护成本。
结束语
设计模式可以使得代码更加优雅,增强代码的扩展性。但是万万不能为了设计模式而设计模式,如果真的这样做了,反而会适得其反,画蛇添足,大幅度增加代码复杂度和降低可维护性。只有在充分的分析业务场景、代码结构的前提下合理的使用设计模式,才能发挥出设计模式最大的作用。
最后一句:设计模式是道法,并不是术法。理解内涵最为重要。