策略模式用于算法的自由切换和扩展,对应于解决某一问题的一个算法族,允许用户从该算法族中任选一个算法解决某一问题,同时可以方便地更换算法和增加新的算法。策略模式实现了算法定义和算法使用的分离,它通过继承和多态的机制实现对算法族的使用和管理,是一个简单,实用的设计模式。
策略模式的定义如下:
策略模式又称为政策(Policy)模式,它是一种对象行为型模式。
1.策略模式结构
其结构如图1所示。

策略模式包含以下三个角色。
(1)Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个功能)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
(2)Strategy(抽象策略类):抽象策略类为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
(3)ConcreteStrategy(具体策略类):具体策略类实现了在抽象策略类中声明的算法,在运行时具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务功能。
2.策略模式实现
策略模式是对算法的封装,他把算法的责任和算法本身分开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列具体策略类里面,作为抽象策略类的子类。在策略模式中对环境类和抽象策略类的理解非常重要,环境类是需要使用算法的类,在一个系统中可以存在多个环境类,它们可能需要重用一些相同的算法。
在使用策略模式时需要将算法从环境类Context中提取出来,首先应该创建一个抽象策略类,其典型代码如下:
package StrategyModel;
public abstract class AbstractStrategy {
//声明抽象算法
public abstract void algorithm();
}
然后封装每一种具体算法的类作为该抽象策略类的子类,代码如下:
package StrategyModel;
public class ConcreteStrategyA extends AbstractStrategy{
//算法的具体实现
@Override
public void algorithm() {
//算法A
}
}
其他策略类与之类似,对于Context而言,在它与抽象策略类之间建立一个关联关系,其典型代码如下:
package StrategyModel;
public class Context {
//维持一个对抽象策略类的引用
private AbstractStrategy abstractStrategy;
public void setAbstractStrategy(AbstractStrategy abstractStrategy){
this.abstractStrategy=abstractStrategy;
}
//调用策略类中的算法
public void algorithm(){
abstractStrategy.algorithm();
}
}
在Context类中定义一个AbstractStrategy类型的对象strategy,通过注入的方式在客户端传入一个具体策略对象,客户端代码如下所示:
package StrategyModel;
public class Client {
public static void main(String[] args){
Context context=new Context();
//可在运行时指定类型,通过配置文件和反射机制实现
AbstractStrategy strategy=new ConcreteStrategyA();
context.setAbstractStrategy(strategy);
context.algorithm();
}
}
在客户端代码中只需注入一个具体策略对象,可以将具体策略类的类名存储在配置文件中,通过反射来动态创建具体策略对象,从而使得用户可以灵活地更换具体策略类,增加新的具体策略类也很方便。策略模式提供了一种可插入式(Pluggable)算法的实现方案。
3.策略模式应用实例


(1)MovieTicket:电影票类,充当环境类。
package StrategyExample;
public class MovieTicket {
private double price;
//维持一个对抽象折扣类的引用
private Discount discount;
public void setPrice(double price){
this.price=price;
}
//注入一个折扣类对象
public void setDiscount(Discount discount){
this.discount=discount;
}
//调用折扣类的折扣计算方法
public double getPrice(){
return discount.calculate(price);
}
}
(2)Discount:折扣类,充当抽象策略类。
package StrategyExample;
public interface Discount {
public double calculate(double price);
}
(3)StudentDiscount:学生票折扣类,充当具体策略类。
package StrategyExample;
public class StudentDiscount implements Discount{
private final double DISCOUNT=0.8;
@Override
public double calculate(double price) {
System.out.println("学生票");
return price*DISCOUNT;
}
}
(4)ChildDiscount类:儿童票折扣类,充当具体策略类。
package StrategyExample;
public class ChildDiscount implements Discount{
private final double DISCOUNT=10;
@Override
public double calculate(double price) {
System.out.println("儿童票");
if(price>=20){
return price=DISCOUNT;
}
else {
return DISCOUNT;
}
}
}
(5)VipDiscount类:Vip会员票折扣类,充当具体策略类。
package StrategyExample;
public class VipDiscount implements Discount{
private final double DISCOUNT=0.5;
@Override
public double calculate(double price) {
System.out.println("VIP票:");
System.out.println("增加积分!");
return price*DISCOUNT;
}
}
(6)配置文件config.xml,在配置文件中存储了具体折扣类的类名。
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<className>StrategyExample.VipDiscount</className>
</config>
(7)XMLUtil:工具类。
package StrategyExample;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.File;
public class XMLUtil {
//该方法用于从XML配置文件中提取具体类的类名,并返回一个实例对象
public static Object getBean() {
//创建DOM文档对象
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
Document document = (Document) documentBuilder.parse(new File("src/main/java/StrategyExample/config.xml"));
//获取包含类名的文档节点
NodeList nodeList=document.getElementsByTagName("className");
Node classNode=nodeList.item(0).getFirstChild();
String cName=classNode.getNodeValue();
//通过类名反射生成实例对象并返回
Class c= Class.forName(cName);
Object obj=c.newInstance();
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
(8)Client:客户端测试类。
package StrategyExample;
public class Client {
public static void main(String[] args){
MovieTicket movieTicket=new MovieTicket();
double originalPrice=60.0;
double currentPrice;
movieTicket.setPrice(originalPrice);
System.out.println("原始价格为:"+originalPrice);
System.out.println("------------------------------");
Discount discount;
//读取配置文件并反射生成具体折扣对象
discount= (Discount) XMLUtil.getBean();
//注入折扣对象
movieTicket.setDiscount(discount);
currentPrice=movieTicket.getPrice();
System.out.println("折后价为:"+currentPrice);
}
}
运行结果如下:
如果需要更改具体策略类,无需修改源代码,只需修改配置文件即可。例如将Vip票改为学生票,只需将存储在配置文件中的具体策略类VipDiscount改为StudentDiscount即可,代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<className>StrategyExample.StudentDiscount</className>
</config>
运行结果如下
如果需要增加新的打折方式,原有代码均无需修改,只需增加一个新的折扣类作为抽象折扣类的子类,实现在抽象折扣类中生命的打折方法,然后修改配置文件,将原有具体折扣类的类名改为新增折扣类的类名即可,完全符合开闭原则。
参考:Java设计模式(刘伟)