概述

在策略模式中,可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法。在这里,每一个封装算法的类都可以称之为一种策略(Strategy),为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做规则的定义,而每种算法则对应于一个具体策略类。

策略模式的主要目的是将算法的定义与使用分开,也就是将算法的行为和环境分开,将算法的定义放在专门的策略类中,每一个策略类封装了一种实现算法,使用算法的环境类针对抽象策略类进行编程,符合依赖倒转原则。在出现新的算法时,只需要增加一个新的实现了抽象策略类的具体策略类即可。

定义

策略模式(Strategy Pattern):定义了一系列算法类,将每一个算法封装起来,并让它们可以互相替换。策略模式让算法独立于使用它的客户而变化,也称之为政策模式(Policy)。策略模式是一种对象行为型模式。

示意图

结构图

  1. Context(环境类):环境类是使用算法的角色,它在解决某个问题(即实现某个方法)时采用了多种策略。在环境类中维持一个对抽象策略类的引用实例,用于定义所采用的策略
  2. Strategy(抽象策略类):它为所支持的算法声明了抽象方法,是所有策略类的父类,它可以是抽象类或具体类,也可以是接口。环境类通过抽象策略类中声明的方法在运行时调用具体策略类中实现的算法。
  3. ConcreteStrategy(具体策略类):它实现了在抽象策略类中声明的算法,在运行时,具体策略类将覆盖在环境类中定义的抽象策略类对象,使用一种具体的算法实现某个业务处理。

策略模式是一个比较容易理解和使用的设计模式策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列具体策略类里面,作为抽象策略类的子类。在策略模式中,对环境类和抽象策略类的理解非常重要,环境类是需要使用算法的类。在一个系统中可以存在多个环境类,他们可能需要重用到一些相同的算法。

在使用策略模式时,需要将算法从Context类中提取出来,首先应该创建一个抽象策略类,其典型的代码如下

	abstract class AbstractStrategy{
		public abstract void algorithm();		//声明抽象算法 
	}

然后再将封装每一种具体算法的类作为该抽象策略类的子类,代码如下

	class ConcreteStrategyA extends AbstractStrategy{
		//算法的具体实现
		public void algrithm(){
			//算法A
		}
	}

在其他具体策略类与之类似,对于Context类而言,在它与抽象策略类之间建立一个关联关系,其典型代码如下

	class Context{
		private AbstractStrategy strategy;	//维持一个对抽象策略类的引用
		
		public void setStrategy(AbstractStrategy strategy){
			this.strategy = strategy;
		}

		//调用策略类中的算法
		public void algorithm(){
			strategy.algorithm();
		}
	}

在Context类中定义一个AbstractStrategy类型的对象strategy,通过注入的方式在客户端传入一个具体策略对象,客户端代码如下:

	....
	Context context = new Context();
	AbtractStrategy strategy;
	strategy = new ConcreteStrategy();	//可在运行时指定类型
	context.setStrategy(strategy);
	context,algorithm();
	....

在客户端代码中只需注入一个具体策略对象,可以将具体策略类类名存储在配置文件中,通过反射来动态创建具体策略对象,从而使得用户可以灵活地更换具体策略类,增加新的具体策略类也挺方便。策略类提供了一种**可插入式(Pluggable)**算法的实现方案

实例:电影票打折方案

使用策略模式设计电影票打折方案,实现了打折算法的复用,并且能够灵活的向系统中增加新的打折方式

设计结构图

结构图

在图中MovieTicket充当环境类角色,Discount充当抽象策略角色,StudentDiscount、ChildrenDiscount和VIPDiscount充当具体策略角色。

代码实现

电影票类:环境类

	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);
		}
	}

折扣类:抽象策略类

	public interface Discount {
		public double calculate(double price);
	}

学生票折扣类:具体策略类

	public class StudentDiscount implements Discount {
	
		@Override
		public double calculate(double price) {
			System.out.println("学生票:");
			return price * 0.8;
		}
	
	}

儿童票折扣类:具体策略类

	public class ChildrenDiscount implements Discount {
	
		@Override
		public double calculate(double price) {
			System.out.println("儿童票:");
			return price - 10;
		}
	
	}

通过工具类XMLUtil来读取配置文件并反射生成对象

	public class XMLUtil{
		//该方法用于从XML配置文件中提取具体类类名,并返回一个实例对象
		public static Object getBean() {
			try {
				//创建对象
				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.newInstance();
				return obj;
			}catch (Exception e) {
				e.printStackTrace();
				return null;
			}
			
		}
	}

配置文件:config.xml

	<?xml version = "1.0"?>
	<config>
		<className>StudentDiscount</className>
	</config>

客户端

	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);
		}
	}

如果需要增加新的打折方式,原有代码均无需修改,只要增加一个新的折扣类作为抽象折扣类的子类,实现在抽象折扣中声明的打折方法,然后修改配置文件,将原有具体打折类类名改名为新增折扣类类名即可,完全符合开闭原则。

总结

策略模式用于算法的自由切换和扩展,它是应用较为广泛的设计模式之一。策略模式对应于解决某一问题的一个算法族,允许用户从该算法族中任选一个算法来解决某一问题,同时可以方便地更换算法或者增加新的算法。只要涉及算法的封装,复用和切换都可以考虑使用策略模式

主要优点

  1. 策略模式提供了开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。

  2. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。

  3. 策略模式提供了一种可以替换继承关系的办法。如果不使用策略模式,那么使用算法的环境类就可能会有一些子类,每一个子类提供不同的算法。但是,这样一来算法的使用就和算法本身混在一起,不符合单一职责原则,决定使用哪一种算法的逻辑和该算法本身混合在一起,从而不可能再独立演化;而且使用继承无法实现算法或行为在程序运行时的动态切换。

  4. 使用策略类可以避免多重条件选择语句,多重条件选择语句不易维护,它把采取哪一种算法或行为的逻辑与算法或行为本身的实现逻辑混合在一起,将它们全部硬编码(Hard Coding)在一个庞大的多重条件选择语句中,比直接继承环境类的办法还要原始和落后。

  5. 策略模式提供了一种算法的复用机制,由于将算法单独提取出来封装在策略类中,因此不用的环境类可以方便地复用这些策略类。

主要缺点

  1. 客户端必须知道所有的策略类,并自行决定使用哪一种策略类,这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法,换言之,策略模式只适用于客户端知道所有的算法或行为的情况

  2. 策略模式将造成系统产生很多具体策略类,任何细小的变化都导致系统要增加一个新的具体策略类。

  3. 无法同时在客户端使用多个策略类,也就是,在使用策略模式时,客户端每次只能使用一个策略类,不支持使用一个策略类完成部分功能后再使用另一个策略类来完成剩余功能。

适用场景

  1. 一个系统需要动态地在几种算法中选择一种,那么可以将这些算法封装到一个个的具体算法中,而这些具体算法类都是一个抽象算法类的子类,换言之,这些具体算法类均有统一的接口,根据里氏代换原则和面向对象的多态性,客户端可以选择使用任何一个具体算法类,并只需维持一个数据类型是抽象算法的对象。

  2. 一个对象很多的行为,如果不用恰当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句。

  3. 不希望客户知道复杂的,与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性和安全性

上次更新: 9/22/2020, 12:20:37 AM