设计模式实战-策略模式,想让你的代码变幻莫测吗?
1、定义
策略模式(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 就好比一个调用入口,隔离底层算法实现。
2、组成角色
策略模式的通用类图如下:
包含的角色罗列如下:
- 上下文角色(Context):该角色一般是一个实现类或者封装类,起到一定的封装及隔离作用,实际接受请求并将请求委托给实际的算法实现类处理,避免外界对底层策略的直接访问;
- 抽象策略角色(Strategy):该角色一般是一个抽象角色,为接口或者抽象类扮演,定义具体策略角色的公共接口;
- 具体策略角色(ConcreteStrategy):实现抽象策略角色的接口,为策略的具体实现类。
3、策略模式代码实现
上文中的类图我们来看下如下用代码实现:
首先是抽象策略角色:
// 抽象策略角色
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();
}
}
4、优缺点
策略模式的优点如下:
- 所有策略放入一组抽象策略接口中,方便统一管理与实现;
策略模式的缺点如下:
- 策略模式每种策略都是单独类,策略很多时策略实现类也很可观;
- 客户端初始化 Context 的时候需要指定策略类,这样就要求客户端要熟悉各个策略,对调用方要求较高。
5、应用场景
策略模式的应用场景如下:
- 需要自由切换算法的场景
- 需要屏蔽算法实现细节的场景
6、使用实例
还是拿我们最上面的排序为例进行说明,对于一个 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]
7、总结
这节我们介绍了策略模式,总的来说比较简单,重点在于策略的切换,虽然说具体策略的实现如何客户端是不可见的,但是客户端进行初始化 Context 上下文角色的时候需要明确知晓系统有多少策略,这就对客户端要求较高了。
- 斐波那契数列与IE9
- DateTime.ToString()输出"年/月/日 时:分:秒"的格式
- Flash在线拍摄用户头象
- win7 64位下如何折腾Tubro C 3.0
- TweenLite的又一应用:图片的拼图加载效果
- mysql创建数据表时如何判断是否已经存在?
- 温故知新:接口的隐式实现与显式实现
- 也谈枚举ToString()性能的改进
- silverlight:利用telerik中的zip类对字符串进行压缩、解压
- 索引,视图,存储过程和触发器文档
- 重点解读:用小程序给公众号涨粉10w的7大行业案例
- 网络域名与注册商标冲突的解决途径
- 网站代码优化我们必须要做的那些事
- 真是热闹! Slade.com等多个域名被曝交易
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 浙大版《C语言程序设计(第3版)》题目集 习题9-4 查找书籍
- 浙大版《C语言程序设计(第3版)》题目集 习题9-5 通讯录排序
- 浙大版《C语言程序设计(第3版)》题目集 习题7-5 找鞍点
- 浙大版《C语言程序设计(第3版)》题目集 练习5-1 求m到n之和
- 手把手教你用Python爬取快递100查询你的物流信息
- 浙大版《C语言程序设计(第3版)》题目集 练习5-2 找两个数中最大者
- 浙大版《C语言程序设计(第3版)》题目集 练习5-3 数字金字塔
- 浙大版《C语言程序设计(第3版)》题目集 习题5-1 符号函数
- 浙大版《C语言程序设计(第3版)》题目集 习题5-2 使用函数求奇数和
- 浙大版《C语言程序设计(第3版)》题目集 习题5-3 使用函数计算两点间的距离
- 浙大版《C语言程序设计(第3版)》题目集 习题5-4 使用函数求素数和
- Linux进程之如何查看进程详情?
- 浙大版《C语言程序设计(第3版)》题目集 习题5-5 使用函数统计指定数字的个数
- 浙大版《C语言程序设计(第3版)》题目集 习题5-6 使用函数输出水仙花数
- 浙大版《C语言程序设计(第3版)》题目集 习题5-7 使用函数求余弦函数的近似值