【设计模式】行为型模式其九: 策略模式
行为型模式其九: 《策略模式》
生活实例
当我们出行旅游时, 可以选择多种出行方式。 每一种出行方式都达到了目的,具体选择哪种取决于游客当前的想法和包包里的money。
策略模式概述
分析
实现某个目标的途径不止一条,可根据实际情况选择一条合适的途径软件开发:
多种算法,例如排序、查找、打折等 使用硬编码(Hard Coding)实现将导致系统违背开闭原则,扩展性差,且维护困难 可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法策略模式定义
定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法可以独立于使用它的客户变化。
又称为政策(Policy)模式
每一个封装算法的类称之为策略(Strategy)类
策略模式提供了一种可插入式(Pluggable)算法的实现方案
策略模式结构
策略模式包含以下3个角色:
Context(环境类) Strategy(抽象策略类) ConcreteStrategy(具体策略类)策略模式实例介绍
问题: 某软件公司为某电影院开发了一套影院售票系统,在该系统中需要为不同类型的用户提供不同的电影票打折方式,具体打折方案如下:
(1) 学生凭学生证可享受票价8折优惠。
(2) 年龄在10周岁及以下的儿童可享受每张票减免10元的优惠(原始票价需大于等于20元)。
(3) 影院VIP用户除享受票价半价优惠外还可进行积分,积分累计到一定额度可换取电影院赠送的礼品。
该系统在将来可能还要根据需要引入新的打折方式。
现使用策略模式设计该影院售票系统的打折方案。
抽象策略类代码
// 这里接口用于定义打折方式
//折扣类:抽象策略类 public interface Discount { public double calculate(double price); }
具体策略类代码
//学生票折扣类:具体策略类
public class StudentDiscount implements Discount { private final double DISCOUNT = 0.8; public double calculate(double price) { System.out.println("学生票:"); return price * DISCOUNT; } }
//VIP会员票折扣类:具体策略类
public class VIPDiscount implements Discount { private final double DISCOUNT = 0.5; public double calculate(double price) { System.out.println("VIP票:"); System.out.println("增加积分!"); return price * DISCOUNT; } }
//儿童票折扣类:具体策略类
public class ChildrenDiscount implements Discount { private final double DISCOUNT = 10; public double calculate(double price) { System.out.println("儿童票:"); if(price>=20) { return price - DISCOUNT; } else { return price; } } }
环境类
环境类(Context)的作用是将具体策略类(Concrete Strategy)封装起来,并提供一些对外接口,使得客户端可以通过环境类使用不同的具体策略类,从而实现不同的行为。环境类包含一个策略类的引用,客户端通过调用环境类的方法来执行相应的策略。
如果不使用环境类,客户端必须直接与具体策略类交互,这样会使得客户端和具体策略类之间的耦合度增加,不利于系统的扩展和维护。同时,如果需要切换不同的策略,客户端必须自己负责管理策略之间的关系,这样会增加客户端的复杂度。
//电影票类:环境类 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(this.price); } }
同样的,为了不修改源代码,我使用XML配置文件
XML配置文件
提醒一下: 里面写的是类的包路径加类名,为了jvm能读取到
<?xml version="1.0"?> <config> <className>designpatterns.strategy.VIPDiscount</className> </config>
XMLUtil读取
public class XMLUtil { //该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象 public static Object getBean() { try { //创建DOM文档对象 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dFactory.newDocumentBuilder(); Document doc; doc =builder.parse(new File("./config.xml")); //获取包含类名的文本节点 NodeList nl = doc.getElementsByTagName("className"); Node classNode=nl.item(0).getFirstChild(); String cName=classNode.getNodeValue(); //通过类名生成实例对象并将其返回 Class c=Class.forName(cName); Object obj=c.getConstructor().newInstance(); return obj; } catch(Exception e) { e.printStackTrace(); return null; } } }
客户端测试
public class Client { public static void main(String args[]) { MovieTicket mt = new MovieTicket(); double originalPrice = 60.0; double currentPrice; mt.setPrice(originalPrice); System.out.println("原始价为:" + originalPrice); System.out.println("---------------------------------"); Discount discount; discount = (Discount)XMLUtil.getBean(); //读取配置文件并反射生成具体折扣对象 mt.setDiscount(discount); //注入折扣对象 currentPrice = mt.getPrice(); System.out.println("折后价为:" + currentPrice); } }
输出:
原始价为:60.0
VIP票:
增加积分!
折后价为:30.0
当我们想要使用不同的策略文件,就只需修改配置文件。
策略模式优缺点
模式优点:
提供了对开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为 提供了管理相关的算法族的办法 提供了一种可以替换继承关系的办法 可以避免多重条件选择语句 提供了一种算法的复用机制,不同的环境类可以方便地复用策略类模式缺点:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类 将造成系统产生很多具体策略类 无法同时在客户端使用多个策略类