定义
策略模式(Strategy Pattern:Define a family of algorithms,encapsulate each one,and make them interchangeable.)中文解释为:定义一组算法,然后将这些算法封装起来,以便它们之间可以互换,属于一种对象行为型模式。总的来说策略模式是一种比较简单的模式,听起来可能有点费劲,其实就是定义一组通用算法的上层接口,各个算法实现类实现该算法接口,封装模块使用类似于 Context 的概念,Context 暴漏一组接口,Context 内部接口委托到抽象算法层。
大家在实际编程中,可能会用到 TreeSet 这种对象,TreeSet 构造时可以传入一个排序实现类以便指定集合元素被遍历时的顺序,当然不传使用默认的自然排序,如下,我们定义一个 TreeSet 并指定排序规则为自然排序的逆序:
TreeSet<String> treeSet = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// 使用自然排序的逆序排列
return o2.compareTo(o1);
}
});
treeSet.add("lily");
treeSet.add("yerkim");
treeSet.add("admin");
for (String s : treeSet) {
System.out.println(s); // yerkim lily admin
}
结果比较明显,依次打印:yerkim lily admin,为什么要提到 TreeSet 这种数据结构,其实策略模式有点类似这种,我们上面所说的通用算法接口就好比 compare 接口,具体算法实现类就好比我们上面自行实现的排序类,而所谓的 Context 就好比一个调用入口,隔离底层算法实现。
组成角色
策略模式的通用类图如下:
包含的角色罗列如下:
- 上下文角色(Context):该角色一般是一个实现类或者封装类,起到一定的封装及隔离作用,实际接受请求并将请求委托给实际的算法实现类处理,避免外界对底层策略的直接访问;
- 抽象策略角色(Strategy):该角色一般是一个抽象角色,为接口或者抽象类扮演,定义具体策略角色的公共接口;
- 具体策略角色(ConcreteStrategy):实现抽象策略角色的接口,为策略的具体实现类。
策略模式代码实现
上文中的类图我们来看下如下用代码实现:
首先是抽象策略角色:
// 抽象策略角色
interface Strategy {
void algorithmInterface();
}
然后是具体策略角色:
// 具体策略角色1
class ConcreteStrategy1 implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("具体策略1");
}
}
// 具体策略角色2
class ConcreteStrategy2 implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("具体策略2");
}
}
最后是我们上下文角色,比较简单,直接贴代码了:
// 上下文角色
class Context {
private Strategy strategy = null;
public Context(Strategy strategy) {
this.strategy = strategy;
}
// 对外接口
public void contextInterface() {
this.strategy.algorithmInterface();
}
}
优缺点
策略模式的优点如下:
- 所有策略放入一组抽象策略接口中,方便统一管理与实现;
策略模式的缺点如下: - 策略模式每种策略都是单独类,策略很多时策略实现类也很可观;
- 客户端初始化 Context 的时候需要指定策略类,这样就要求客户端要熟悉各个策略,对调用方要求较高。
应用场景
策略模式的应用场景如下:
- 需要自由切换算法的场景
- 需要屏蔽算法实现细节的场景
使用实例
还是拿我们最上面的排序为例进行说明,对于一个 List 的字符串集合,我们使用不同的排序策略,比如自然排序、逆序两种策略,注意我们这里把排序规则称之为一种排序策略或算法实现,首先是要定义我们的抽象策略角色:
// 字符串的抽象排序策略
interface IStringSortStrategy {
List<String> sort(List<String> list);
}
这里我们只定义了一个排序的策略接口,入参出参均是字符串列表,下面看看该策略的两种实现:
// 排序策略——正序
class StringSortStrategyNormal implements IStringSortStrategy{
@Override
public List<String> sort(List<String> list) {
Collections.sort(list);
return list;
}
}
// 排序策略——倒序
class StringSortStrategyReverse implements IStringSortStrategy{
@Override
public List<String> sort(List<String> list) {
Collections.sort(list);
Collections.reverse(list);
return list;
}
}
然后是我们的上下文角色:
// 上下文角色
class StringSortContext {
private IStringSortStrategy strategy;
public StringSortContext(IStringSortStrategy strategy) {
this.strategy = strategy;
}
// 获取排序结果
public List<String> getSortList(List<String> list) {
return this.strategy.sort(list);
}
}
上下文角色中定义了一个外部调用的 api 接口 getSortList,这样我们只需要初始化 StringSortContext 的时候指定排序策略,再调用 getSortList 即可获取排序结果,具体的排序策略如何实现对客户端是不可见的。测试类就是我们的 main 方法:
List<String> list = new ArrayList<>(3);
list.add("admin");
list.add("code-shop");
list.add("lucy");
StringSortContext context = new StringSortContext(new StringSortStrategyReverse());
List<String> reverseSortedList = context.getSortList(list);
System.out.println(reverseSortedList); // [lucy, code-shop, admin]
StringSortContext context2 = new StringSortContext(new StringSortStrategyNormal());
List<String> normalSortedList = context2.getSortList(list);
System.out.println(normalSortedList); // [admin, code-shop, lucy]
总结
这节我们介绍了策略模式,总的来说比较简单,重点在于策略的切换,虽然说具体策略的实现如何客户端是不可见的,但是客户端进行初始化 Context 上下文角色的时候需要明确知晓系统有多少策略,这就对客户端要求较高了。