概述
策略模式(Strategy Pattern)基本介绍
1、在策略模式中,定义算法族,分别封装起来。让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户端。是一种行为型模式。
2、策略模式体现了几个设计原则:第一、把变化的代码从不变的代码中分离出来;第二、针对接口变成而不是具体的类(定义了策略接口);第三、多用组合/聚合,少用继承(客户端通过组合方式使用策略模式)。
3、在策略模式中,定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法,在这里,每一个封装算法的类就是一种策略,为了保证这些策略使用时具有一致性,一般情况下一提供一个抽象的策略类来做规则定义,每种算法对应一个具体的算法类。
4、策略模式的主要目的是将算法的定义与使用分开,也就是即哪个算法的行为和环境分开,将算法定义在专门的策略类中,每一个策略类封装了一种实现算法,使用算法的环境类针对抽象策略类进行编程,符合依赖倒转原则,在出现新的算法类时,只需要增加一个新的具体策略类即可,
类图描述
角色分析
1、Context:环境类,使用算法的角色,它在实现某个方法时,可能使用多种策略,在环境类中聚合一个抽象策略类(拥有一个抽象策略类的成员变量),用于定义采用的策略。
2、Strategy,抽象策略类,它为所支持的算法声明了抽象方法,是所有具体策略类的父类,环境类通过抽象策略中声明的方法,在运行时调用具体策略类中实现的算法。
3、ConcreteStrategy,具体策略类,它实现在抽象策略类中声明的算法,在运行时,使用具体策略类中的某个方法实现业务逻辑的处理。
案例
景区门票折扣方案,不同的用户给予不同的优惠策略,比如年费会员免费入场、学生半价入场、儿童半价入场,成人95折入场。
package com.example.pattern.strategy;
import lombok.Getter;
import lombok.Setter;
/**
* 策略模式
*
* @author zjt
* @date 2021-01-07
*/
interface Discount { // 抽象策略类
double calculate(Double price);
}
@Getter
@Setter
public class Ticket { // 环境类 门票
private double price;
private Discount discount;
public Ticket(double price) {
this.price = price;
}
public double getPrice() {
return discount.calculate(price);
}
}
class StudentDiscount implements Discount { // 具体策略类 学生票
@Override
public double calculate(Double price) {
return price * 0.5;
}
}
class ChildDiscount implements Discount { // 具体策略类 儿童票
@Override
public double calculate(Double price) {
return price * 0.5;
}
}
class MemberDiscount implements Discount { // 具体策略类 会员票
@Override
public double calculate(Double price) {
return 0;
}
}
class AdultDiscount implements Discount { // 具体策略类 会员票
@Override
public double calculate(Double price) {
return price * 0.95;
}
}
class Client {
public static void main(String[] args) {
double price = 100;
Ticket ticket = new Ticket(price);
System.out.println("原价为:" + price);
ticket.setDiscount(new AdultDiscount());
price = ticket.getPrice();
System.out.println("原价为:" + price);
}
}
应用分析
public static void main(String[] args) {
Integer[] arr = {1, 4, 2, 3};
// 实现升序排序 反回-1放左边,返回1放右边
// 说明
// 1、实现了Comparator接口(类似与策略对象) 匿名类对象
// 2、函数式接口实现 new Comparator<Integer>() {} 就是实现了策略接口的对象
// 3、public int compare(Integer o1, Integer o2) {} 指定具体的处理方式
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1 > o2) {
return 1;
} else {
return -1;
}
}
};
Arrays.sort(arr, comparator);
// 调用方法的源码
//public static <T> void sort(T[] a, Comparator<? super T> c) {
// if (c == null) {
// sort(a); // 默认方法
// } else {
// if (LegacyMergeSort.userRequested)
// legacyMergeSort(a, c); // 使用策略对象
// else
// TimSort.sort(a, 0, a.length, c, null, 0, 0);
// }
// }
System.out.println(Arrays.toString(arr));
// 实现方式2 lambda 表达式 实现策略模式
Arrays.sort(arr, (o1, o2) -> {
if (o1 < o2) {
return 1;
} else {
return -1;
}
});
System.out.println(Arrays.toString(arr));
}
题外话 Arrays.asList()方法不能add的小坑
提到了Arrays工具类,前几天我在单元测试的时候发现了个问题,Arrays.asList()增加元素报错,顺便多提一点关于Arrays.asList()方法使用小坑,先看一段代码
public static void main(String[] args) {
Integer[] irr = {1, 2, 3, 4};
List<Integer> list = Arrays.asList(irr);
list.add(5);
}
// 运行报错
// Exception in thread "main" java.lang.UnsupportedOperationException
// at java.util.AbstractList.add(AbstractList.java:148)
// at java.util.AbstractList.add(AbstractList.java:108)
// 报错所在代码 是在AbstractList中抛出的错误,
// public boolean add(E e) {
// add(size(), e);
// return true;
// }
// public void add(int index, E element) {
// throw new UnsupportedOperationException();
// }
因为我自己没有仔细看源码,第一眼只看到了下面的代码,下意识的认为是返回的java.util.ArrayList,还困扰了我一会儿,java.util.ArrayList源码中找了一下,发现确实实现了新增的各种方法,最后才发现问题原因,在代码中备注一下。
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
// 这个ArrayList是Arrays工具类的内部静态类,它所实现的方法有限,
// 并没有实现 add() remove() 方法。需要额外操作还需要进一步转换
// 比如 List<Integer> list = new ArrayList<>(Arrays.asList(arr)) ;
return new ArrayList<>(a);
}
内部静态类代码就在asList() 方法的下面一行。记录一下,以后看代码还是要认真的多看看。
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
...... // 内容省略 实现的大致方法有 size(),toArray(),get(int index),
// set(int index, E element),indexOf(Object o),contains(Object o)
// sort(Comparator<? super E> c),replaceAll(UnaryOperator<E> operator)
// forEach(Consumer<? super E> action)
}
好了,回归策略模式。
总结
优点
1、策略模式的关键是:分析项目中变化部分与不变的部分。
2、策略模式提体现了,多用组合/聚合,少用继承,用行为的组合,而不是行为的继承。
3、策略模式体现了“开闭原则”的支持,用户可以在不修改原有系统的基础上选择算法或者行为,也可以灵活的增加新的算法或行为。
4、策略模式提供了管理算法族的方案,定义一个算法或者行为族,恰当的使用避免重复代码。提供了算法复用机制,不同的环境类可以方便的复用这些算法。同时也避免了大量的条件判断语句。
5、策略模式提供了替换继承关系的办法:将算法封装在独立抽象策略类中,也就是可以独立与环境类去改变它,使得易于切换、理解和扩展。
缺点:
1、每增加一个策略,就要增加一个类,过多的策略会导致类的数目庞大。同时策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类。
2、客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。