摘要生成中...
AI 摘要
Hunyuan-lite

image.png

Strategy 策略,在编程里可以理解为算法。在策略模式中一个类的行为或其算法可以在运行时更改。在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。

策略模式 Strategy Design Pattern

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.——GoF
定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端。

所谓客户端 Client 指的就是使用到算法的地方。

🍐⚱️:里面就是组合加委托。

登场角色:

  • Strategy(策略):决定实现策略所必须的接口(API)。
  • ConcreteStrategy(具体的策略):负责实现 Strategy 角色的接口(API),即负责实现具体的策略(战略、方向、方法和算法)
  • Context(上下文/环境):负责使用 Strategy 角色。它保存了 ConcreteStrategy 角色的实例,并使用 ConcreteStrategy 角色去实现需求(调用 Strategy 角色的接口(API))。Context 类可以通过 站内文章依赖注入站内文章简单工厂 等方式来获取具体策略对象。

image.png

使用方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 运行时动态确定,根据配置文件的配置决定使用哪种策略
public static void main(String[] args) throws Exception {
Strategy strategy = null;
Properties props = new Properties();
props.load(new FileInputStream("./config.properties"));
String type = props.getProperty("strategy_type");
strategy = StrategyFactory.getStrategy(type);

// 装填策略
UserCache userCache = new UserCache(strategy);
//...
}


// 非运行时动态确定,在代码中指定使用哪种策略
public static void main(String[] args) {
//...
Strategy strategy = new LruEvictionStrategy();
UserCache userCache = new UserCache(strategy);
//...
}

特意编写 Strategy 角色目的就是将算法与其他部分分离开来,只定义了与算法相关的接口(API),然后在程序中以委托的方式来使用算法。使用委托这种弱关联关系可以很方便地整体替换算法。

我们可以在程序中切换 ConcreteStrategy 角色。比如在内存少的时候用速度慢但省内存的策略。或者可以用某种算法去「验算」另一种算法。

如果我们使用策略工厂创建策略对象,当需要增加新的策略时,为了避免代码修改,可以对工厂进行改造:通过加载配置文件或搜索使用自定义注解标注的策略类,通过 站内文章反射 动态加载策略类并创建策略对象。

优点 缺点
可以在运行时切换对象内的算法。 如果你的算法极少发生改变,那么没有任何理由引入新的类和接口。使用该模式只会让程序过于复杂。
可以将算法的实现和使用算法的代码隔离开来。 客户端必须知晓策略间的不同——它需要选择合适的策略。
可以使用组合来代替继承。 许多现代编程语言支持函数类型功能,允许你在一组匿名函数中实现不同版本的算法。这样,你使用这些函数的方式就和使用策略对象时完全相同,无需借助额外的类和接口来保持代码简洁。
开闭原则。无需对上下文进行修改就能够引入新的策略。

相关的设计模式:

  • 站内文章享元模式:有时会使用享元模式让多个地方可以共用 ConcreteStrategy 角色。
  • 站内文章抽象工厂
    • 使用策略模式可以整体地替换算法
    • 使用抽象工厂模式则可以整体地替换具体工厂、零件和产品
  • 站内文章状态模式:使用策略模式和状态模式都可以替换被委托对象,而且它们的类之间的关系也很相似。但是两种模式的目的不同。
    • 在策略模式中,ConcreteStrategy 角色是表示算法的类。在策略模式中,可以替换被委托对象的类。当然如果没有必要,也可以不替换
    • 而在状态模式中,ConcreteState 角色是表示「状态」的类。在状态模式中,每次状态变化时,被委托对象的类都必定会被替换。
  • 站内文章模板方法模式
    • 模板方法模式基于继承机制:它允许你通过扩展子类中的部分内容来改变部分算法。
    • 策略基于组合机制:你可以通过对相应行为提供不同的策略来改变对象的部分行为。
    • 模板方法在类层次上运作,因此它是静态的。策略在对象层次上运作,因此允许在运行时切换行为。

不同模式中解耦的对象:

  • 工厂模式:解耦对象的创建和使用
  • 观察者模式:解耦观察者和被观察者
  • 策略模式:解耦策略的定义、创建和使用

策略模式和「多态」感觉很像,但这是两个东西。策略模式是一种代码编写模式,侧重于应用场景;多态是一种技术,侧重于代码实现。

策略模式优化代码示例

背景:编写对文件进行排序的程序,电脑内存只有 8G。文件中只包含整型数,并且,相邻的数字通过逗号来区隔。

  • 当文件小于 6GB 时,我们可以将文件读到内存使用快速排序算法排好序,再写入到文件中。
  • 当文件大于 6GB 且小于 10GB 时,使用外部排序算法进行排序。
  • 当文件大于 10GB 小于 100GB 时,利用 CPU 多核优势,在外部排序的基础上使用多线程进行优化。
  • 当文件大于 100GB 时,使用 MapReduce 框架,利用多机的处理能力,提高排序的效率。

一个基础的实现方式

算法骨架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Sorter {
private static final long GB = 1000 * 1000 * 1000;

public void sortFile(String filePath) {
// 省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
if (fileSize < 6 * GB) { // [0, 6GB)
quickSort(filePath);
} else if (fileSize < 10 * GB) { // [6GB, 10GB)
externalSort(filePath);
} else if (fileSize < 100 * GB) { // [10GB, 100GB)
concurrentExternalSort(filePath);
} else { // [100GB, ~)
mapreduceSort(filePath);
}
}

private void quickSort(String filePath) {/* 省略逻辑 */}
private void externalSort(String filePath) {/* 省略逻辑 */}
private void concurrentExternalSort(String filePath) {/* 省略逻辑 */}
private void mapreduceSort(String filePath) {/* 省略逻辑 */}
}

使用方式:

1
2
3
4
5
6
public class SortingTool {
public static void main(String[] args) {
Sorter sorter = new Sorter();
sorter.sortFile(args[0]);
}
}

存在问题:多种算法实现集中在 Sorter 类中,文件过大,影响到可读性、可维护性。排序算法为私有函数,影响可复用性。

策略的抽取

下面将算法抽取为类,将其与业务逻辑解耦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface ISortAlg {
void sort(String filePath);
}

public class QuickSort implements ISortAlg {
@Override
public void sort(String filePath) {/* 省略逻辑 */}
}

// 省略其他策略类

public class Sorter {
private static final long GB = 1000 * 1000 * 1000;

public void sortFile(String filePath) {
// 省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
ISortAlg sortAlg;
if (fileSize < 6 * GB) { // [0, 6GB)
sortAlg = new QuickSort();
} else if (fileSize < 10 * GB) { // [6GB, 10GB)
sortAlg = new ExternalSort();
} else if (fileSize < 100 * GB) { // [10GB, 100GB)
sortAlg = new ConcurrentExternalSort();
} else { // [100GB, ~)
sortAlg = new MapReduceSort();
}
sortAlg.sort(filePath);
}
}

观察上面代码我们可以发现,每种策略都是无状态的,我们没必要在每次使用的时候重新创建一个新的对象。

使用工厂模式优化策略类的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class SortAlgFactory {
private static final Map<String, ISortAlg> algs = new HashMap<>();

static {
algs.put("QuickSort", new QuickSort());
algs.put("ExternalSort", new ExternalSort());
algs.put("ConcurrentExternalSort", new ConcurrentExternalSort());
algs.put("MapReduceSort", new MapReduceSort());
}

public static ISortAlg getSortAlg(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
return algs.get(type);
}
}

public class Sorter {
private static final long GB = 1000 * 1000 * 1000;

public void sortFile(String filePath) {
// 省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
ISortAlg sortAlg;
if (fileSize < 6 * GB) { // [0, 6GB)
sortAlg = SortAlgFactory.getSortAlg("QuickSort");
} else if (fileSize < 10 * GB) { // [6GB, 10GB)
sortAlg = SortAlgFactory.getSortAlg("ExternalSort");
} else if (fileSize < 100 * GB) { // [10GB, 100GB)
sortAlg = SortAlgFactory.getSortAlg("ConcurrentExternalSort");
} else { // [100GB, ~)
sortAlg = SortAlgFactory.getSortAlg("MapReduceSort");
}
sortAlg.sort(filePath);
}
}

目前的代码已经符合策略模式的结构了,策略模式将策略的定义、创建和使用解耦。

使用查表逻辑优化 if-else 代码

上面的代码中策略创建时存在 if-else 逻辑,如果你的 if-else 逻辑过于复杂的话,考虑使用查表逻辑优化创建过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 查表逻辑
public class Sorter {
private static final long GB = 1000 * 1000 * 1000;
private static final List<AlgRange> algs = new ArrayList<>();
static {
algs.add(new AlgRange(0, 6*GB, SortAlgFactory.getSortAlg("QuickSort")));
algs.add(new AlgRange(6*GB, 10*GB, SortAlgFactory.getSortAlg("ExternalSort")));
algs.add(new AlgRange(10*GB, 100*GB, SortAlgFactory.getSortAlg("ConcurrentExternalSort")));
algs.add(new AlgRange(100*GB, Long.MAX_VALUE, SortAlgFactory.getSortAlg("MapReduceSort")));
}

public void sortFile(String filePath) {
// 省略校验逻辑
File file = new File(filePath);
long fileSize = file.length();
ISortAlg sortAlg = null;
for (AlgRange algRange : algs) {
if (algRange.inRange(fileSize)) {
sortAlg = algRange.getAlg();
break;
}
}
sortAlg.sort(filePath);
}

// 查表的表项
private static class AlgRange {
private long start;
private long end;
private ISortAlg alg;

public AlgRange(long start, long end, ISortAlg alg) {
this.start = start;
this.end = end;
this.alg = alg;
}

public ISortAlg getAlg() {
return alg;
}

public boolean inRange(long size) {
return size >= start && size < end;
}
}
}

文本绘图代码备份

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Context{
strategy
{method} contextMethod
}

abstract class Strategy{
{method} {abstract} strategyMethod
}

class ConcreteStrategy1{
{method} strategyMethod1
}

class ConcreteStrategy2{
{method} strategyMethod2
}

Context o-> Strategy
Strategy <|-- ConcreteStrategy1
Strategy <|-- ConcreteStrategy2

本文参考