策略模式
源码: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、策略模式造成很多的策略类。