设计模式 | 六、策略模式[StrategyPattern]

策略模式

源码:https://github.com/GiraffePeng/design-patterns

1、定义

策略模式(Strategy Pattern)定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

2、应用场景

策略模式在很多地方用到,如 Java SE 中的容器布局管理就是一个典型的实例,Java SE 中的每个容器都存在多种布局供用户选择。在程序设计中,通常在以下几种情况中使用策略模式较多。

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。

3、实现举例

策略模式的主要角色如下。

  • 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

3.1、生活场景举例

用策略模式来实现生活中鱼的做法,这里模拟两种鱼的做法,红烧鱼和清蒸鱼。
对于客户(客户端)来说,如果他只是想吃鱼(做一些事),并没有要求鱼的口味或者做法,我们就可以使用策略模式来实现,因为不管哪种做法(算法或者实现)都满足客户的要求,对于客户来说不受影响(多个算法或实现间可替换)。

下面我们创建抽象策略类CookService.java

public interface CookService {

	public void makeFish(Fish fish);
}

创建具体策略类(红烧鱼),实现上述接口

public class CookBraiseService implements CookService{

	@Override
	public void makeFish(Fish fish) {
		System.out.println("红烧"+fish.getWeight()+"斤"+fish.getNameType());
	}
}

创建具体策略类(清蒸鱼),实现上述接口

public class CookSteamedService implements CookService{

	@Override
	public void makeFish(Fish fish) {
		System.out.println("清蒸"+fish.getWeight()+"斤"+fish.getNameType());
	}
}

创建Fish鱼实体类

public class Fish implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = -8617430338527775356L;

	//鱼的种族名称
	private String nameType;
	
	//鱼的重量(斤)
	private double weight;

	public String getNameType() {
		return nameType;
	}

	public void setNameType(String nameType) {
		this.nameType = nameType;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}

	public Fish(String nameType, double weight) {
		this.nameType = nameType;
		this.weight = weight;
	}

	public Fish() {
	}
}

创建环境类,持有策略类的引用,负责对策略类的调用,属于行为型模式

public class Kitchen {

	private CookService cookService;
	
	public Kitchen(CookService cookService) {
		this.cookService = cookService;
	}
	
	public Kitchen() {
	}

	public void makeFish(Fish fish) {
		cookService.makeFish(fish);
	}
}

创建测试类

public class CookingTest {

	public static void main(String[] args) {
		Fish fish = new Fish("鲈鱼", 12.2);
		Kitchen kitchen = new Kitchen(new CookSteamedService());
		kitchen.makeFish(fish);
	}
}

打印结果:

清蒸12.2斤鲈鱼

上述的写法我们将具体策略类的创建交给了客户端,这种方式我们还可以进行改造,在实际场景中,客户端一般会传入一个特殊的Key值来标明我想要哪种实现方式或算法。对Kitchen进行改造

public class Kitchen {

	private CookService cookService;
	
	public void makeFishByType(String type,Fish fish) {
		
		if(type.equals("steamed")) {
			cookService = new CookSteamedService();
		}else if(type.equals("braise")) {
			cookService = new CookBraiseService();
		}else {
			notFoundException();
		}
		cookService.makeFish(fish);
	}

	private void notFoundException() {
		throw new RuntimeException("not found type");
	}
	
}

这时,客户端只需要传入指定的做鱼方式和fish实体类即可

public class CookingTest {

	public static void main(String[] args) {
		Fish fish = new Fish("鲈鱼", 12.2);
		Kitchen kitchen = new Kitchen();
		kitchen.makeFishByType("steamed", fish);
	}
}

这时你会发现上述代码中的Kitchen类相当于对策略的具体实现类的调度分发,故Kitchen中也结合了委派模式来实现。
既然使用到了委派,我们根据上一篇文章中的写法,我们还可以对Kitchen类进行改造,使用容器来保存需要调用的类,同时为了防止Kitchen类容器中的每个类在调用时重新创建,我们还可以使用单例模式来防止这种情况的发生.

public class Kitchen {

	
	private static Map<String,CookService> cookMap = new ConcurrentHashMap<String,CookService>();
	
	static {
		cookMap.put(CookType.BRAISE.toString(), new CookBraiseService());
		cookMap.put(CookType.STEAMED.toString(), new CookSteamedService());
	}
	
	private Kitchen() {}

	public static void makeFishByTypeName(String type,Fish fish) {
		CookService cookService = cookMap.get(type);
		if(cookService == null) {
			notFoundException();
		}
		cookService.makeFish(fish);
	}
	
	private static void notFoundException() {
		throw new RuntimeException("not found type");
	}
	
	enum CookType {
		BRAISE,
		STEAMED;
	}
}

测试代码调整:

public class CookingTest {

	public static void main(String[] args) {
		Fish fish = new Fish("鲈鱼", 12.2);
		Kitchen.makeFishByTypeName(Kitchen.CookType.BRAISE.toString(), fish);
	}
}

类图如下所示:
在这里插入图片描述

3.2、项目需求场景举例

实际的互联网项目中肯定会有支付场景,对于支付方式则会有多种可供用户选择,针对用户而言,哪有支付方式的最终结果都是订单的状态发生改变,变为已支付。对于这种场景我们也可以使用策略模式来实现。
创建抽象支付类

public abstract class PayAbstract {

	//获取支付名称
	public abstract String getName();
	
	//查询余额
	protected abstract double queryBalance(String uid);
	
	//扣款支付
	public PayState pay(String uid,double amount) {
		//调用子类的实现方法获取子类中的余额
		if(queryBalance(uid) < amount){
			return new PayState(500,"支付失败","余额不足");
		}
		return new PayState(200,"支付成功","支付金额:" + amount);
	}
}

创建三种支付方式,京东支付,支付宝支付,微信支付

public class AliPay extends PayAbstract{

	@Override
	public String getName() {
		return "支付宝支付";
	}

	@Override
	protected double queryBalance(String uid) {
		return 500;
	}
}
public class JDPay extends PayAbstract{

	@Override
	public String getName() {
		return "京东支付";
	}

	@Override
	protected double queryBalance(String uid) {
		return 200;
	}
}
public class WechatPay extends PayAbstract{

	@Override
	public String getName() {
		return "微信支付";
	}

	@Override
	protected double queryBalance(String uid) {
		return 900;
	}
}

创建支付结果的封装类PayState.java

public class PayState {

	private int code;
	private Object data;
	private String msg;
	
	public PayState(int code, String msg,Object data) {
		this.code = code;
		this.data = data;
		this.msg = msg;
	}
	
	public String toString(){
		return ("支付状态:[" + code + "]," + msg + ",交易详情:" + data);
	}
}

创建用户的订单数据传输类Order.java

public class Order {

	private String uid;
	
	private String orderSn;
	
	private double price;
	

	public Order(String uid, String orderSn, double price) {
		super();
		this.uid = uid;
		this.orderSn = orderSn;
		this.price = price;
	}

	public String getUid() {
		return uid;
	}

	public void setUid(String uid) {
		this.uid = uid;
	}

	public String getOrderSn() {
		return orderSn;
	}

	public void setOrderSn(String orderSn) {
		this.orderSn = orderSn;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}
	
}

创建策略模式中的环境类,负责分发策略以及策略的方法调用

public class PayManager {

	private static Map<String,PayAbstract> payManagers = new ConcurrentHashMap<String, PayAbstract>(); 
	static {
		payManagers.put(PayType.JDPAY.toString(), new JDPay());
		payManagers.put(PayType.ALIPAY.toString(), new AliPay());
		payManagers.put(PayType.WECHANTPAY.toString(), new WechatPay());
	}
	
	private PayManager(){ };
	
	public static void pay(String payType,Order order) {
		PayAbstract payAbstract = payManagers.get(payType);
		System.out.println("欢迎使用" + payAbstract.getName() + "支付");
		PayState pay = payAbstract.pay(order.getUid(), order.getPrice());
		System.out.println("支付结果" + pay.toString());
	}
	
	enum PayType{
		JDPAY,
		ALIPAY,
		WECHANTPAY;
	}
}

创建测试类,模拟客户端

public class PayTest {

	public static void main(String[] args) {
		Order order = new  Order("2121", "21322121", 700);
		PayManager.pay(PayManager.PayType.WECHANTPAY.toString(), order);
	}
}

控制台打印结果:

欢迎使用微信支付支付
支付结果支付状态:[200],支付成功,交易详情:支付金额:700.0

最后我们来看下类图
在这里插入图片描述
定义了一个抽象支付类,然后每种支付方式去继承该类,实现其自己的支付方法。创建结果集PayState来包装结果数据,创建Order来传输订单数据,创建PayManager负责管理这些策略类,主要用于依赖策略类,然后分发和调度。

4、策略模式在源码中的体现

4.1、JDK体现

首先来看一个比较常用的比较器 Comparator 接口,我们看到的一个大家常用的compare()方法,就是一个策略抽象实现:

@FunctionalInterface
public interface Comparator<T> {
	int compare(T o1, T o2);
}

Comparator 抽象下面有非常多的实现类
在这里插入图片描述
我们经常会把 Comparator 作为参数传入作为排序策略,例如 Arrays 类的 parallelSort 方法等:

public class Arrays {
	....
	public static <T> void parallelSort(T[] a, Comparator<? super T> cmp) {
		...
	}
	public static <T> void parallelSort(T[] a, int fromIndex, int toIndex,
                                        Comparator<? super T> cmp) {
   		...
    }
	....
}

还有 TreeMap 的构造方法:

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{
	....
	public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }
	....
}

4.2、在Spring中的运用

来看 Resource 类:

package org.springframework.core.io;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;

import org.springframework.lang.Nullable;
public interface Resource extends InputStreamSource {

	boolean exists();
	
	default boolean isReadable() {
		return true;
	}

	default boolean isOpen() {
		return false;
	}

	default boolean isFile() {
		return false;
	}

	URL getURL() throws IOException;
	
	URI getURI() throws IOException;
	
	File getFile() throws IOException;

	default ReadableByteChannel readableChannel() throws IOException {
		return Channels.newChannel(getInputStream());
	}

	long contentLength() throws IOException;

	long lastModified() throws IOException;

	Resource createRelative(String relativePath) throws IOException;

	@Nullable
	String getFilename();

	String getDescription();
}

我们来看他的子类
在这里插入图片描述
还有一个非常典型的场景,Spring 的初始化也采用了策略模式,不同的类型的类采用不同的初始化策略。首先有一个 InstantiationStrategy 接口,我们来看一下源码

package org.springframework.beans.factory.support;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.lang.Nullable;
public interface InstantiationStrategy {
	Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3) throws
	BeansException;
	
	Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3,
				Constructor<?> var4, @Nullable Object... var5) throws BeansException;
				
	Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, @Nullable
					Object var4, Method var5, @Nullable Object... var6) throws BeansException;
}

顶层的策略抽象非常简单,但是它下面有两种策略 SimpleInstantiationStrategy 和CglibSubclassingInstantiationStrategy,我们看一下类图:
在这里插入图片描述
打开类图我们还发现 CglibSubclassingInstantiationStrategy 策略类还继承了SimpleInstantiationStrategy 类,说明在实际应用中多种策略之间还可以继承使用.

5、策略模式的优缺点

5.1、优点

  • 1、多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
  • 2、策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  • 3、策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  • 4、策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  • 5、策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

5.2、缺点

  • 1、客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  • 2、策略模式造成很多的策略类。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值