策略模式
策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。

模式动机
一天, 某软件团队打算为游客们创建一款导游程序。 该程序的核心功能是提供美观的地图, 以帮助用户在任何城市中快速定位。
用户期待的程序新功能是自动路线规划: 他们希望输入地址后就能在地图上看到前往目的地的最快路线。
程序的首个版本只能规划公路路线。 驾车旅行的人们对此非常满意。 但很显然, 并非所有人都会在度假时开车。 因此软件团队在下次更新时添加了规划步行路线的功能。 此后, 该软件团队又添加了规划公共交通路线的功能。
而这只是个开始。 不久后, 该软件团队又要为骑行者规划路线。 又过了一段时间, 该软件团队又要为游览城市中的所有景点规划路线。

尽管从商业角度来看, 这款应用非常成功, 但其技术部分却让人非常头疼: 每次添加新的路线规划算法后, 导游应用中主要类的体积就会增加一倍。 终于在某个时候, 该软件团队觉得自己没法继续维护这堆代码了。
无论是修复简单缺陷还是微调街道权重, 对某个算法进行任何修改都会影响整个类, 从而增加在已有正常运行代码中引入错误的风险。
此外, 团队合作将变得低效。 如果该软件团队在应用成功发布后招募了团队成员, 他们会抱怨在合并冲突的工作上花费了太多时间。 在实现新功能的过程中, 团队需要修改同一个巨大的类, 这样他们所编写的代码相互之间就可能会出现冲突。
这种情形在软件开发中很容易出现。比如某软件团队要开发一款模拟鸭子的游戏。游戏需求很简单,鸭子分为红鸭子和绿鸭子两种,要会叫和游泳;红鸭子显示红色;绿鸭子显示绿色。最初团队设计了如下图所示的类。

但是该代码完成后,产品经理让软件团队为所有的鸭子加上飞的功能。软件团队在上面代码的基础上,在父类中加上fly()函数,以实现所有鸭子飞的功能。其类图如下:

之后,游戏为了增加趣味性,加入了橡胶小黄鸭。

此时游戏出现了bug——小黄鸭会飞;因为小黄鸭是橡胶鸭子,应该不会飞,所以出现了Bug。为了消除该Bug,软件团队在小黄鸭类中重写了fly()函数,让小黄鸭不会飞。

但是,随着游戏大受欢迎,游戏中加入了更多其它种类的鸭子,比如蓝鸭、紫鸭、橙鸭等等,这些鸭子有的会飞,有得不会飞。对于那些不会飞的鸭子,每次加入后,都需要重写fly()方法,太过于繁琐。利用Java提供的接口,将fly()设计成接口,也就是下面的类结构

这样,如果鸭子不会飞,则不实现flyable的接口。但是这种设计也会带来下面的问题:
1. 红鸭子和绿鸭子的fly()代码是重复的。
2. 如果fly()功能的需求被修改,那么所有实现fly()接口的类均需要修改代码。
因此需要找到一个更为巧妙的方法,以应对这种需求的改变,使得代码的改动量达到最小。
其中最根本的问题在于fly()方法的需求是变动的。因为在父类下增加子类,是不确定子类代表的鸭子是否会飞。解决的途径也非常简单——将变化的代码和不会变化的代码分开。这样变化的代码就不会影响不会发生变化代码。
按照上面的思路,可以将上面的例子做下面的改动。

上图中,左侧的代码是不会被改动的类;右侧子图是fly()方法所在的接口,会经常被修改:一个是飞的实现,一个是不会飞的实现。可以将flyBehavior的一个对象当场鸭子的一个属性。这样,如果鸭子会飞,则初始化为flyWithWing类的对象,通过该对象调用fly()方法,来实现该子类鸭子会飞的功能;对于不会飞的鸭子子类,则在类中初始化一个flyNoWay的对象,用该对象实现鸭子不会飞的功能。

这样不论飞的功能如何变化,其影响的仅仅是上图右侧部分,而不会影响上图左侧部分。而且不会带来代码重复,以及功能发生变化要修改多处的问题。上面的模式就是策略模式。
策略模式建议找出负责用许多不同方式完成特定任务的类, 然后将其中的算法抽取到一组被称为策略的独立类中。
名为上下文的原始类必须包含一个成员变量来存储对于每种策略的引用。 上下文并不执行任务, 而是将工作委派给已连接的策略对象。
上下文不负责选择符合任务需要的算法——客户端会将所需策略传递给上下文。 实际上, 上下文并不十分了解策略, 它会通过同样的通用接口与所有策略进行交互, 而该接口只需暴露一个方法来触发所选策略中封装的算法即可。
因此, 上下文可独立于具体策略。 这样你就可在不修改上下文代码或其他策略的情况下添加新算法或修改已有算法了。

在导游应用中, 每个路线规划算法都可被抽取到只有一个 buildRoute生成路线方法的独立类中。 该方法接收起点和终点作为参数, 并返回路线中途点的集合。
即使传递给每个路径规划类的参数一模一样, 其所创建的路线也可能完全不同。 主要导游类的主要工作是在地图上渲染一系列中途点, 不会在意如何选择算法。 该类中还有一个用于切换当前路径规划策略的方法, 因此客户端 (例如用户界面中的按钮) 可用其他策略替换当前选择的路径规划行为。
总而言之,策略模式的动机为:
完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。
定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体策略类。
模式定义
策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。
策略模式是一种对象行为型模式,通过对算法进行封装,把算法的使用和算法的实现分割开来,并通过算法的抽象接口对象对这些算法进行管理。
模式结构
策略模式包含如下角色:
Context: 上下文类。持有一个策略类的引用,最终给客户端调用。
Strategy: 抽象策略类。抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
ConcreteStrategy: 具体策略类。实现了抽象策略定义的接口,提供具体的算法实现或行为。

模式时序图

模式抽象代码分析
不使用策略模式的代码:

使用策略模式重构后的抽象代码。
抽象策略类:

具体策略类:

上下文类:

客户端:

模式分析
策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是准备一组算法,并将每一个算法封装起来,使得它们可以互换。
在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。
策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。
模式实例
在本例中, 策略模式被用于在电子商务应用中实现各种支付方法。 客户选中希望购买的商品后需要选择一种支付方式: 支付宝或者信用卡。
具体策略不仅会完成实际的支付工作, 还会改变支付表单的行为, 并在表单中提供相应的字段来记录支付信息。
抽象的策略类——通用的支付方法接口

具体策略类——使用支付宝支付

具体策略类——使用信用卡支付

信用卡类:

上下文类——订单类

客户端代码

模式优点
策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
策略模式提供了管理相关的算法族的办法。
策略模式提供了可以替换继承关系的办法。
使用策略模式可以避免使用多重条件转移语句。
模式缺点
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
模式适用环境
如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
一个系统需要动态地在几种算法中选择一种。
如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。
模式应用
1. Java 8 开始支持 lambda 方法, 它可作为一种替代策略模式的简单方式。
2. 核心 Java 程序库中策略模式的示例:
对 java.util.Comparator#compare() 的调用来自 Collections#sort().
javax.servlet.http.HttpServlet: service()方法, 还有所有接受 HttpServletRequest和 HttpServletResponse对象作为参数的 doXXX()方法。
javax.servlet.Filter#doFilter()
3. JavaSE的容器布局管理就是策略模式应用的一个经典实例。

识别方法: 策略模式可以通过允许嵌套对象完成实际工作的方法以及允许将该对象替换为不同对象的设置器来识别。
模式扩展
可以通过上下文类状态的个数来决定是使用策略模式还是状态模式。
策略模式的上下文类自己选择一个具体策略类,具体策略类无须关心上下文类;而状态模式的上下文类由于外在因素需要放进一个具体状态中,以便通过其方法实现状态的切换,因此上下文类和状态类之间存在一种双向的关联关系。
使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,上下文类的状态会根据用户的操作自动转换。
如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。
总结
在策略模式中定义了一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式。策略模式是一种对象行为型模式。
策略模式包含三个角色:环境类在解决某个问题时可以采用多种策略,在环境类中维护一个对抽象策略类的引用实例;抽象策略类为所支持的算法声明了抽象方法,是所有策略类的父类;具体策略类实现了在抽象策略类中定义的算法。
策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。
策略模式主要优点在于对“开闭原则”的完美支持,在不修改原有系统的基础上可以更换算法或者增加新的算法,它很好地管理算法族,提高了代码的复用性,是一种替换继承,避免多重条件转移语句的实现方式;其缺点在于客户端必须知道所有的策略类,并理解其区别,同时在一定程度上增加了系统中类的个数,可能会存在很多策略类。
策略模式适用情况包括:在一个系统里面有许多类,它们之间的区别仅在于它们的行为,使用策略模式可以动态地让一个对象在许多行为中选择一种行为;一个系统需要动态地在几种算法中选择一种;避免使用难以维护的多重条件选择语句;希望在具体策略类中封装算法和与相关的数据结构。

