一. 定义
在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如:旅游出行可乘坐飞机、火车、大巴或开车自驾游等方式进行,
当实现某个功能存在多种算法或策略时:可根据环境或条件的不同,选择不同的算法或策略来完成该功能,如数据排序策略有冒泡、选择、插入排序
如果使用多重条件转移语句实现(即硬编码),不但使条件语句变的很复杂,而且增加、删除或更换算法要修改源码不易维护,违背开闭原则;
1. 策略模式(Strategy Pattern)
1) 定义了一系列算法(算法族),并将每个算法分别封装起来,使它们之间可相互替换,且算法的变化不会影响使用算法的用户,
2) 策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和实现分割开来,并委派给不同的对象对这些算法进行管理。
2. 这算法体现了几个设计原则
策略模式的关键是: 分析项目中变化的部分 与 不变的部分;
1) 把变化的代码从不变的代码中分离出来;
2) 针对接口编程而不是具体类(定义了策略接口);
3) 核心思想: 多用组合/聚合,少用继承(客户通过组合方式使用策略),用行为类组合,而不是行为的继承,更有弹性
二. 特点
1. 优点
-
多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if...else语句、switch...case语句;
-
策略模式提供了一系列的可供重用的算法族,恰当的使用可以把算法族的公共代码转移到父类中,从而避免重复代码
-
策略模式可提供行为的不同实现,客户可根据不同时间或空间要求选择不同的策略;
-
策略模式提供了对开闭原则的完美支持,可在不修改原码的情况下,灵活增加新算法;
-
策略模式把算法的使用放到环境类Context中,而算法的实现移到具体策略类Strategy中,实现了二者的分离;
2. 缺点
-
客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类;
-
策略模式造成很多的策略类,当策略过多时会导致类数目庞大,增加维护难度;
三. 应用场景
-
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中;
-
一个类操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句;
-
系统中各算法彼此完全独立,且要求客户隐藏具体算法的实现细节(数据结构);
-
多个类只区别在表现的行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为;
四. 模式的结构
定义算法簇,分别封装起来,让他们之间可互相替换,让算法的变化独立于使用算法的客户.
策略模式结构图
策略模式角色分析
1.抽象策略类 (Strategy): 定义一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用该接口调用不同的算法;
2.具体策略类(Concrete Strategy):实现了抽象策略定义的接口,提供具体的算法实现;
3.环境类(Context): 持有一个策略类的引用,最终给客户端调用;
五. 模式的实现
编写鸭子项目,具体要求如下:
-
有各种鸭子(比如 野鸭、北京鸭、绍兴鸭、水鸭,鸭子有各种行为,比如叫、飞行等)
-
显示鸭子的信息;
-
传统方案解决
-
其他鸭子,都继承了Duck类,所以fly让所有子类都会飞了,swim让所有子类都会游泳这是不正确的;
-
上面说1的问题,其实是继承带来的问题,对类的局部改动,尤其超类的局部改动,会影响其他部分,会有溢出效应
-
为了改进问题1,我们可以通过覆盖fly方法解决 ==>覆盖解决,但如果我们有一个玩具鸭ToyDuck 这样就需要覆盖Duck两个方法;
2.使用策略模式解决
把原来变化的代码从不变的代码中分离出来,定义的是策略接口,针对接口编程,尽量不使用继承,更改为组合或聚合;
1> 实例结构图
2> 相关代码实现
object StrategyClient {
@JvmStatic
fun main(args: Array<String>) {
val wildDuck=WildDuck()
wildDuck.display()
wildDuck.fly()
wildDuck.swim()
val pekingDuck=PekingDuck()
pekingDuck.display()
pekingDuck.fly()
pekingDuck.swim()
val toyDuck=ToyDuck()
toyDuck.display()
toyDuck.fly()
toyDuck.swim()
}
}
//Strategy 飞行行为策略接口
interface FlyBehavior {
fun fly()
}
//ConcreteStrategy 擅长飞行策略的具体实现
class GoodFlyBehavior : FlyBehavior {
override fun fly() {
println("~擅长飞行~")
}
}
//不会飞行策略具体实现
class NoFlyBehavior : FlyBehavior {
override fun fly() {
println("~不会飞行~")
}
}
//游泳行为策略接口
interface SwimBehavior {
fun swim()
}
//擅长游泳策略具体实现
class GoodSwimBehavior : SwimBehavior {
override fun swim() {
println("~擅长游泳~")
}
}
//不会游泳策略具体实现
class NoSwimBehavior : SwimBehavior {
override fun swim() {
println("~不会游泳~")
}
}
//Context 环境类
abstract class Duck {
var flyBehavior: FlyBehavior? = null//飞行策略接口
var swimBehavior: SwimBehavior? = null//游泳策略接口
abstract fun display() //显示鸭子信息
//飞行
fun fly() {
flyBehavior?.fly()
}
//游泳
fun swim() {
swimBehavior?.swim()
}
}
class WildDuck : Duck() {
init {
flyBehavior = GoodFlyBehavior()
swimBehavior = GoodSwimBehavior()
}
override fun display() {
println("这是野鸭子")
}
}
class PekingDuck : Duck() {
init {
flyBehavior = NoFlyBehavior()
swimBehavior = GoodSwimBehavior()
}
override fun display() {
println("~~这是北京鸭~~~")
}
}
class ToyDuck : Duck() {
init {
flyBehavior = NoFlyBehavior()
swimBehavior = NoSwimBehavior()
}
override fun display() {
println("这是玩具鸭~")
}
}
程序运行结果
这是野鸭子
~擅长飞行~
~擅长游泳~
~~这是北京鸭~~~
~不会飞行~
~擅长游泳~
这是玩具鸭~
~不会飞行~
~不会游泳~
3. 策略模式在JDK-Arrays 应用源码分析
1>JDk的Arrays的Comparato就使用了策略模式
public interface Comparator<T> {
int compare(T o1, T o2);
}
public class Test {
public static void main(String[] args) {
Integer[] data = {9, 1, 2, 8, 4, 3};
/**实现升序排序,返回-1放左边,1放右边,0保持不变
*1.实现了Comparator接口,匿名类对象, new Comparator<Integer>(){...}
*2.对象new Comparator<Integer>{...} 就是实现了策略接口的对象
*3. public int compare(Integer o1 Integer o2){} 指定具体的处理方式
*/
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return (o1 < o2) ? -1 : ((o1 > o2) ? 1 : 0);
}
};
Arrays.sort(data, comparator);
System.out.println(Arrays.toString(data));//升序
//方式2 -同时lambda 表达式实现
Integer[] data2 = {19, 11, 12, 18, 14, 13};
Arrays.sort(data2, (var1, var2) -> {
if (var1.compareTo(var2) > 0) {
return -1;
} else {
return 1;
}
});
System.out.println(Arrays.toString(data2));//降序
}
// public static <T> void sort( T[] a, Comparator<? super T> c) {
// if (c == null) {
// sort(a);//按默认的方法排序
// } else {
// TimSort.sort(a, 0, a.length, c, null, 0, 0); //按给定的方法 c 进行排序
// }
// }
}
六. 策略模式的扩展
在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度,其结构图如图 5 所示。