场景引入:在我们的生活中我们可以通过很多种不同的方式来完成一件事情,这里的每一种方式都可以称作为一种策略。我们可以根据环境、条件等因素的不同选择不同的策略来完成这件事情。比如说出去旅游,我们可以选择坐火车、坐飞机、坐大巴、骑自行车,甚至徒步等等方式。如果想舒适快速我们可以选择飞机,节约钱我们可以选择火车和大巴,离家近的我们可以骑自行车,每一种方式都可以到达目的地。但是总有一种方式是当前最适合你的。你会选择哪种出行方式呢?
定义:定义一系列算法,将每一个算法独立封装起来,并让它们可以相互替换。策略模式让算法可以独立于使用它的客户而改变。
策略模式结构图
由上图可知,策略模式包含以下三个角色
(1) Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个功能)时可以采用多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略。
(2) Strategy(抽象策略类):抽象策略类为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或者具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
(3) ConcreteStrategy(具体策略类):具体策略类实现了在抽象策略类中声明的算法,在运行时具体策略类将覆盖在环境类所定义的抽象策略类对象,使用一种具体的算法实现某个业务功能。
策略模式应用实例
- 实例说明
某软件公司为某电影院开发了一套电影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票的打折方式,具体打折方案如下:
(1)学生凭学生证可享受票价8折优惠
(2)年龄在10周岁及以下的儿童可享受每张票减免10元的优惠(原始票价需大于等于20元)
(3)影院VIP用户除享受票价半折优惠外还可进行积分,积分累计到一定额度可换取电影院赠送的礼品
该系统在将来可能还要引入新的打折方式
2. 实例类图
3.代码示例
1 package designpatterns.strategy; 2 3 public interface Discount { 4 public double calculate(double price); 5 }
1 package designpatterns.strategy; 2 3 public class MovieTicket { 4 private double price; 5 private Discount discount; //维持一个对抽象折扣类的引用 6 7 public double getPrice() { 8 return discount.calculate(price); 9 } 10 public void setPrice(double price) { 11 this.price = price; 12 } 13 14 public void setDiscount(Discount discount) { 15 this.discount = discount; 16 } 17 18 }
1 package designpatterns.strategy; 2 3 public class StudentDiscount implements Discount{ 4 private final double DISCOUNT = 0.8; 5 @Override 6 public double calculate(double price) { 7 System.out.println("学生票:"); 8 return price * DISCOUNT; 9 } 10 }
1 package designpatterns.strategy; 2 3 public class ChildrenDiscount implements Discount{ 4 private final double DISCOUNT = 10; 5 @Override 6 public double calculate(double price) { 7 System.out.println("儿童票:"); 8 if (price >= 20) { 9 return price - DISCOUNT; 10 }else { 11 return price; 12 } 13 } 14 }
1 package designpatterns.strategy; 2 3 public class VIPDiscount implements Discount{ 4 private final double DISCOUNT = 0.5; 5 @Override 6 public double calculate(double price) { 7 System.out.println("VIP票:"); 8 System.out.println("增加积分!"); 9 return price * DISCOUNT; 10 } 11 }
1 <?xml version="1.0"?> 2 <config> 3 <className>designpatterns.strategy.StudentDiscount</className> 4 </config>
1 package designpatterns.strategy; 2 3 import java.io.File; 4 import javax.xml.parsers.*; 5 import org.w3c.dom.*; 6 7 public class XMLUtil { 8 public static Object getBean() { 9 try { 10 //创建DOM文件对象 11 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); 12 DocumentBuilder builder = dFactory.newDocumentBuilder(); 13 Document doc; 14 doc = builder.parse(new File("src//designpatterns//strategy//config.xml")); 15 16 //获取包含类名的文本节点 17 NodeList nList = doc.getElementsByTagName("className"); 18 Node classNode = nList.item(0).getFirstChild(); 19 String cName = classNode.getNodeValue(); 20 21 //通过类名生成实例对象并将其返回 22 Class c = Class.forName(cName); 23 Object obj = c.newInstance(); 24 return obj; 25 } catch (Exception e) { 26 e.printStackTrace(); 27 return null; 28 } 29 } 30 }
1 package designpatterns.strategy; 2 3 public class Client { 4 public static void main(String[] args) { 5 MovieTicket mt = new MovieTicket(); 6 double originalPrice = 60; 7 double currentPrice; 8 9 mt.setPrice(originalPrice); 10 System.out.println("原始价格为:" + originalPrice); 11 System.out.println("---------------------------"); 12 13 Discount discount; 14 discount = (Discount)XMLUtil.getBean(); 15 mt.setDiscount(discount); 16 17 currentPrice = mt.getPrice(); 18 System.out.println("折后价为:" + currentPrice); 19 } 20 }
4.运行结果
如果需要更换具体策略类,无需修改源代码,只需修改配置文件即可。例如将VIP票改成学生票,只需在config.xml中将StudentDiscount改为VIPDiscount,代码如下:
1 <?xml version="1.0"?> 2 <config> 3 <className>designpatterns.strategy.VIPDiscount</className> 4 </config>
运行结果:
经典应用实例---JavaSE中的布局管理器
策略模式的优/缺点与适用环境
1. 策略模式优点
(1)策略模式提供了对开闭原则的完美支持
(2)策略模式提供了管理相关算法族的办法
(3)策略模式提供了一种可以替换继承关系的办法
(4)适用策略模式可以避免使用多重条件选择语句
(5)策略模式提供了一种算法的复用机制
2. 策略模式缺点
(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类
(2)策略模式将造成系统产生很多具体的策略类,任何细小的变化都导致系统要增加一个新的具体策略类
(3)无法同时在客户端使用多个策略类
3. 策略模式适用环境
在以下几种情况可以考虑使用策略模式
(1)一个系统需要动态的在几种算法中选择一种
(2)一个对象有很多行为,如果不用恰当的模式,这些行为只好使用多重条件选择语句来实现
(3)不希望客户端知道复杂的、与算法相关的数据结构,在具体的策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性