策略模式(Strategy Pattern)
策略模式
策略模式最早的时候是在马老师的坦克大战看的,讲的很干,也挺清楚。现在回想起来更是记忆犹新。
说到策略模式,最应该关注的应该就是策略这个词语了吧。这个词我直接贴一段百度的翻译,大家看一下
提取和设计模式相关的两个含义
- 可以实现目标的方案集合
- 根据不同情况选择不同的策略
然后我们再看看标准的策略模式定义
定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
有了这个基本的清晰概念再学起来就很轻松了。
策略模式类图 📌
定义了一个策略接口,然后将每种不同的策略实现同一个接口。这样各个策略之间就可以进行切换。在使用的过程中,可以将策略当成参数传到具体的方法中执行(这里要用函数接口),或者在客户端调用之前就将指定好具体的策略。
站点的主题切换 🎨
相信大家都使用过可以切换主题的站点,这次我也是尝试使用策略模式来实现这个功能。
需求:
- 用户可以根据自己的喜欢自行选择预设的 3 个主题配色
3 个主题配色:
- 七彩斑斓的黑
- 背景色:backgroundColor 黑色
- 字体颜色:fontColor 灰色
- 五颜六色的黑
- 背景色:backgroundColor 黑灰色
- 字体颜色:fontColor 白色
- 绚烂多彩的黑
- 背景色:backgroundColor 灰黑色
- 字体颜色:fontColor 黑色
代码实现:
/**
* Theme 主题接口
* <p>
* 欢迎跟我一起学习,微信(lvgocc)公众号:星尘的一个朋友
*
* @author lvgorice@gmail.com
* @version 1.0
* @blog @see http://lvgo.org
* @CSDN @see https://blog.csdn.net/sinat_34344123
* @date 2020/11/21
*/
public interface Theme {
void show();
}
/**
* Context
* <p>
* 欢迎跟我一起学习,微信(lvgocc)公众号:星尘的一个朋友
*
* @author lvgorice@gmail.com
* @version 1.0
* @blog @see http://lvgo.org
* @CSDN @see https://blog.csdn.net/sinat_34344123
* @date 2020/11/21
*/
public class Context {
private Theme theme;
public Theme getTheme() {
return theme;
}
public void setTheme(Theme theme) {
this.theme = theme;
}
public void show() {
theme.show();
}
}
测试代码
/**
* ThemeTest
* <p>
* 欢迎跟我一起学习,微信(lvgocc)公众号:星尘的一个朋友
*
* @author lvgorice@gmail.com
* @version 1.0
* @blog @see http://lvgo.org
* @CSDN @see https://blog.csdn.net/sinat_34344123
* @date 2020/11/21
*/
class ThemeTest {
@Test
void show() {
Context context = new Context();
System.out.println("七彩斑斓的黑");
context.setTheme(new ColorfulBlack());
context.show();
System.out.println("五颜六色的黑");
context.setTheme(new MotleyBlack());
context.show();
System.out.println("绚烂多彩的黑");
context.setTheme(new SplendidBlack());
context.show();
}
}
测试结果
七彩斑斓的黑
- 背景色:backgroundColor 黑色
- 字体颜色:fontColor 灰色
五颜六色的黑
- 背景色:backgroundColor 黑灰色
- 字体颜色:fontColor 白色
绚烂多彩的黑
- 背景色:backgroundColor 灰黑色
- 字体颜色:fontColor 黑色
其实要说这个策略模式的实现,它本身就是这么一个非常简单的写法。只是可以通过更多的思想融入可以使其变得更加灵活好用,同时也会变得复杂起来。这里一起看看一个经典的策略模式的实现,就是 JDK 中的比较器。在 JDK 中就是不同的类型有不同的比较算法,这也是符合了策略模式的思想。我再把策略模式的定义拿过来看一看
定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
JDK 为每种不同的数据类型定义了一系列的算法,并将每个算法封装起来,使他们可以通过 Comparator 接口进行相互替换,对于客户端来讲,尽管调用比较方法就可以了,即使算法发生了改变(替换其他算法)也不会影响到客户端的使用。
JDK 比较器
JDK 中的比较实现有两种,一种是直接通过实现 Compareable 接口,定义其他对象与自己的顺序(比较)。而另一种就是通过使用策略模式来实现的比较器 Comparator 接口。接下来就一起看看 JDK 是怎么运用策略模式设计的比较器。
首先是策略模式中的第一个关键的定义,定义一系列算法。根据开闭原则,这里定义了一个接口,然后将具体的一系列算法实现延迟到子类当中去。
比较器 - 策略接口
public interface Comparator<T>
一系列算法 - 具体策略
String 的比较算法
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String>, java.io.Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 8575799808933029326L;
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
int min = Math.min(n1, n2);
for (int i = 0; i < min; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2) {
c1 = Character.toUpperCase(c1);
c2 = Character.toUpperCase(c2);
if (c1 != c2) {
c1 = Character.toLowerCase(c1);
c2 = Character.toLowerCase(c2);
if (c1 != c2) {
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
return n1 - n2;
}
/** Replaces the de-serialized object. */
private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
}
如果对象本身支持比较,即实现了 Comparable 接口的对象,可以使用 Comparator 提供的下面这个方法
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
其中 Comparators 这个类是专门为 Comparator 接口创建比较算法使用的默认类,是一个同包访问权限的类
/**
* Package private supporting class for {@link Comparator}.
*/
class Comparators {
private Comparators() {
throw new AssertionError("no instances");
}
/**
* Compares {@link Comparable} objects in natural order.
*
* @see Comparable
*/
enum NaturalOrderComparator implements Comparator<Comparable<Object>> {
INSTANCE;
@Override
public int compare(Comparable<Object> c1, Comparable<Object> c2) {
return c1.compareTo(c2);
}
@Override
public Comparator<Comparable<Object>> reversed() {
return Comparator.reverseOrder();
}
}
....
省略部分代码
....
}
其实对于比较这种算法,更多的是由使用者自己来决定谁大谁小,JDK 仅提供了一些基本的比较策略。比如如下几种比较策略
// 常规比较
static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return NaturalOrderComparator.INSTANCE;
}
// 空值小于非空
static <T> Comparator<T> nullsFirst(Comparator<? super T> var0) {
return new NullComparator(true, var0);
}
// 空值大于非空
static <T> Comparator<T> nullsLast(Comparator<? super T> var0) {
return new NullComparator(false, var0);
}
// 等等,如果还想了解可以自行查看 java/util/Comparator.java
注意:两个空值比较只是提供了当两个元素为空时的比较策略,当两个比较元素都不为空时需使用调用者提供的比较算法
下面我们一起看下如何使用 JDK 的比较器来达到策略模式定义的第二点它们可以相互替换,且算法的改变不会影响使用算法的客户。
‘你’ 大还是 ‘好’ 大
我们就拿 ‘你‘ 和 ’好’ 这两个汉字来尝试一下。
public class TestJDKComparator {
public static void main(String[] args) {
String you = "你";
String fine = "好";
// String 的比较器
int ctic = you.compareToIgnoreCase(fine);
System.out.println("compareToIgnoreCase:" + ctic);
// JDK 提供的默认比较器
Comparator<String> comparator = Comparator.naturalOrder();
int compare = comparator.compare(you, fine);
System.out.println("naturalOrder = " + compare);
// 自定义比较器1
Comparator<String> stringComparator1 = Comparator.nullsFirst((o1, o2) -> {
byte[] bytes1 = o1.getBytes();
byte[] bytes2 = o2.getBytes();
return bytes1.length - bytes2.length;
});
int compare1 = stringComparator1.compare(you, fine);
System.out.println("nullsFirst&customComparator1 = " + compare1);
// 自定义比较器2
Comparator<String> stringComparator2 = Comparator.nullsFirst((o1, o2) -> {
int length1 = o1.length();
int length2 = o2.length();
int min = Math.min(length1, length2);
for (int i = 0; i < min; i++) {
char o1Char = o1.charAt(i);
char o2Char = o2.charAt(i);
if (o1Char != o2Char) {
return o2Char - o1Char;
}
}
return length2 - length1;
});
int compare2 = stringComparator2.compare(you, fine);
System.out.println("nullsFirst&customComparator2 = " + compare2);
}
}
测试结果
compareToIgnoreCase:-2589
naturalOrder = -2589
nullsFirst&customComparator1 = 0
nullsFirst&customComparator2 = 2589
在 JDK 的比较器中有一个方法,可以让我们学习。就是 naturalOrder() 返回来的这个比较器。
// 常规比较
static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return NaturalOrderComparator.INSTANCE;
}
enum NaturalOrderComparator implements Comparator<Comparable<Object>> {
INSTANCE;
// 实现比较器定义的抽象方法
@Override
public int compare(Comparable<Object> c1, Comparable<Object> c2) {
// 使用参数自己的“策略”
return c1.compareTo(c2);
}
@Override
public Comparator<Comparable<Object>> reversed() {
return Comparator.reverseOrder();
}
}
这个比较器里有一个参数限制,而这个参数限制的就是必须是 Comarable 的实现类,同时是这个实现类的子类。其实,这个参数就是一个策略模式的“策略接口”,传入的参数就是具体的策略。因为这个传入的参数必须要实现 compareTo 这个方法,也就是实现 Comarable 接口的抽象方法。
public interface Comparable<T> {
int compareTo(T var1);
}
在 JDK 中更灵活的使用比较器就是使用匿名类的写法
总结 📚
其实抛开这个模式本身,我们在一些逻辑实现的时候也会使用这种写法,最简单的就是对一个接口方法的实现。使得他们可以在不同的情况下进行不同的切换。所以,在我们系统中,如果可能出现一些相同的操作,但是却会有很多不同的实现的时候,就是在使用这种“策略模式”来实现。每个具体的实现算法不同,但是他们的操作是相同。使用开闭原则来控制算法的入口,具体的实现延迟到子类。但当我们的具体算法变多的时候,使用起来可能会有一些副作用,所以这个时候可以考虑使用工厂模式来辅助策略模式变得更好用。
其他设计模式:点击查看