1. 是什么⭐️🔴

  • 1)策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间==可以互相替换==。此模式让算法的变化==独立于使用算法的客户==,并委派给不同的对象对这些算法进行管理。
  • 2)这算法体现了几个设计原则
    • 第一、把变化的代码从不变的代码中分离出来
    • 第二、针对接口编程而不是具体类(定义了策略接口)
    • 第三、多用组合/聚合,少用继承(客户通过组合方式使用策略

2. 模式结构

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

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

3. UML 图示

3.1. 原理图

image.png

说明:从上图可以看到,客户 Context 有成员变量 Strategy 或者其他的策略接口。至于需要使用到哪个策略,可以在构造器中指定

  1. 环境类 (Context)聚合不同的抽象策略类 (Strategy)
  2. Context构造函数导入抽象策略类 (Strategy)
  3. 客户端调用时传入 Context 构造函数具体策略类(Concrete Strategy)

4. 案例分析⭐️🔴

%%
▶3.🏡⭐️◼️【🌈费曼无敌🌈⭐️第一步⭐️】◼️⭐️-point-20230410-1022%%
❕ ^zx08me

4.1. 商城促销

1. 环境类 (SalesMan)聚合抽象策略类 (Strategy) 2. 环境类 (SalesMan)构造函数导入抽象策略类 (Strategy) 3. 客户端调用时传入 Context 角色类 SalesMan 的构造函数具体策略类 (Concrete Strategy) 4. 切换策略时,使用 SalesMan 的 setter 方法,set 不同的策略

示例代码:[[SalesMan.java]]
%%
01222232-🏡⭐️◼️策略模式的简单用法是什么逻辑?1. Context 类聚合 + 构造导入 Strategy 接口;2. Client 调用时,传入不同的 Strategy 实现类给 Context 的构造函数◼️⭐️-point-202301222232%%

4.2. 主打特性

比如某个型号手机主打拍照策略,另一个型号主打游戏策略等

image.png

  1. 环境类 (Duck)聚合不同的抽象策略类 (XXXBehavior),并通过setter 方法引入
  2. 不同的环境类的子类 (XXXDuck) 继承 Duck 后,就能直接使用 Duck 聚合的 FlyBehavior 等策略接口
  3. 不同的环境类 (XXXDuck) 的子类通过调用各自的策略接口设置不同的 Behavior

示例代码:[[pages/002-schdule/001-Arch/001-Subject/013-DemoCode/DesignPattern/src/com/atguigu/strategy/improve/Duck.java]]

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
public abstract class Duck {  
//属性, 策略接口
FlyBehavior flyBehavior;
//其它属性<->策略接口
QuackBehavior quackBehavior;

public Duck() {

}

public abstract void display();//显示鸭子信息

public void quack() {
System.out.println("鸭子嘎嘎叫~~");
}

public void swim() {
System.out.println("鸭子会游泳~~");
}

public void fly() {
//改进
if (flyBehavior != null) {
flyBehavior.fly();
}
}

public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}

public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
1
2
3
4
5
6
7
8
9
10
11
public class WildDuck extends Duck {  
//构造器,传入FlyBehavor 的对象
public WildDuck() {
flyBehavior = new GoodFlyBehavior();
}

@Override
public void display() {
System.out.println(" 这是野鸭 ");
}
}
1
2
3
public interface FlyBehavior {  
void fly(); // 子类具体实现
}
1
2
3
4
5
6
7
8
public class NoFlyBehavior implements FlyBehavior{  

@Override
public void fly() {
// TODO Auto-generated method stub
System.out.println(" 不会飞翔 ");
}
}

5. 优缺点⭐️🔴

5.1. 优点

  • 策略类之间可以自由切换
    由于策略类都实现同一个接口,所以使它们之间可以自由切换
  • 易于扩展
    增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“,避免了使用多重转移语句(if...else if...else
  • 通过 set 方法动态替换
    提供了可以替换继承关系的办法:策略模式将算法封装在独立的 Strategy 类中,使得我们可以独立于其 Context 改变它,使它易于切换、易于理解、易于扩展
    比如下面代码中的 pekingDuck set 了其他 flyBehavior
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static void main(String[] args) {  
    // TODO Auto-generated method stub
    WildDuck wildDuck = new WildDuck();
    wildDuck.fly();//
    ToyDuck toyDuck = new ToyDuck();
    toyDuck.fly();

    PekingDuck pekingDuck = new PekingDuck();
    pekingDuck.fly();

    //动态改变某个对象的行为, 北京鸭 不能飞
    pekingDuck.setFlyBehavior(new NoFlyBehavior());
    System.out.println("北京鸭的实际飞翔能力");
    pekingDuck.fly();
    }

5.2. 缺点

  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。

6. 适用场景⭐️🔴

  1. 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  2. 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  3. 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
    %%
    0805-🏡⭐️◼️策略模式的适用场景?1. 需要在不同算法动态选择时;2. 多种行为在大量 ifelse 之间判断时;3. 多种算法独立,需要对 client 透明时◼️⭐️-point-202301250805%%

7. JDK 源码分析⭐️🔴

7.1. Arrays 的 Comparator

%%
01222232-🏡⭐️◼️Arrays 的 sort 方法是用了什么设计模式?模式中的角色分别是什么?Arrays 是 Context 角色,控制具体策略的使用;Comparator 接口是 Strategy 接口;传入的 Comparator 的匿名内部类就是具体的策略◼️⭐️-point-202301222232%%

Comparator 中的策略模式。在 Arrays 类中有一个 sort() 方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
public class Arrays{
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);
}
}
}

Arrays 就是一个环境角色类,这个 sort 方法可以传一个新策略让 Arrays 根据这个策略来进行排序。就比如下面的测试类。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class demo {
public static void main(String[] args) {

Integer[] data = {12, 2, 3, 2, 4, 5, 1};
// 实现降序排序
Arrays.sort(data, new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
System.out.println(Arrays.toString(data)); //[12, 5, 4, 3, 2, 2, 1]
}
}

这里我们在调用 Arrays 的 sort 方法时,第二个参数传递的是 Comparator 接口的子实现类对象。所以 Comparator 充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色。也就是我们自定义的匿名内部类 public int compare(Integer o1, Integer o2) {xxx}。环境角色类(Arrays)应该持有抽象策略的引用来调用。那么,Arrays 类的 sort 方法到底有没有使用 Comparator 子实现类中的 compare() 方法吗?让我们继续查看 TimSort 类的 sort() 方法,代码如下:

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
class TimSort<T> {
static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c,
T[] work, int workBase, int workLen) {
assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length;

int nRemaining = hi - lo;
if (nRemaining < 2)
return; // Arrays of size 0 and 1 are always sorted

// If array is small, do a "mini-TimSort" with no merges
if (nRemaining < MIN_MERGE) {
int initRunLen = countRunAndMakeAscending(a, lo, hi, c);
binarySort(a, lo, hi, lo + initRunLen, c);
return;
}
...
}

private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) {
assert lo < hi;
int runHi = lo + 1;
if (runHi == hi)
return 1;

// Find end of run, and reverse range if descending
if (c.compare(a[runHi++], a[lo]) < 0) { // Descending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0)
runHi++;
reverseRange(a, lo, runHi);
} else { // Ascending
while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0)
runHi++;
}

return runHi - lo;
}
}

上面的代码中最终会跑到 countRunAndMakeAscending() 这个方法中。我们可以看见,只用了 compare 方法,所以在调用 Arrays.sort 方法只传具体 compare 重写方法的类对象就行,这也是 Comparator 接口中必须要子类实现的一个方法。

8. 策略模式与桥接模式的区别⭐️🔴

%%
01222233-🏡⭐️◼️桥接模式和策略模式的区别?桥接模式和策略模式各自的 UML 及例子是什么?◼️⭐️-point-202301222233%%

[[策略模式与桥接模式区别-pudn.com]]

8.1. 相同点

策略模式
image.png

image.png

桥接模式

image.png

image.png

核心逻辑都是使用了聚合 + 构造导入

8.2. 不同点

%%
01230717-🏡⭐️◼️策略模式和桥接模式的区别?变化不同:桥接模式涉及到多个维度,抽象化类和实现化类都涉及到变化,而策略模式中 Context 不涉及变化,主要是在不同的抽象化 Strategy 中进行选择。目的不同:桥接模式是将不同维度的抽象,通过聚合和构造导入的方式,使得松散的扩展抽象化类和具体实现化类变的更加紧密,目的是高内聚。而策略模式是将算法逻辑封装起来与调用者进行隔离,目的是解耦达到低耦合的目的。范畴不同:桥接模式通过不同维度的聚合、组合加构造导入的方式,以达到更多层次构建系统的目的。而策略模式主要在策略一个层次上进行选择,格局比桥接模式低一点。桥接模式在范围上是包含策略模式的。◼️⭐️-point-202301230717%%

8.2.1. 变化不同

在桥接模式中不仅 Implementor 具有变化(ConcreateImplementior),而且 Abstraction 也可以发生变化(RefinedAbstraction),而且两者的变化是完全独立的,RefinedAbstraction 与 ConcreateImplementior 之间松散耦合,它们仅仅通过 Abstraction 与 Implementor 之间的关系联系起来。而在策略模式中,并不考虑 Context 的变化,只有算法的可替代性。因此桥接模式一般比策略模式更加复杂。

8.2.2. 目的不同

  1. 在桥接模式中不仅定义 Implementor 的接口而且定义 Abstraction 的接口,Abstraction 的接口不仅仅是为了与 Implementor 通信而存在的,这也反映了结构型模式的特点:通过继承、聚合的方式组合类和对象以形成更大的结构属于结构型模式,目的可以理解为高内聚
  2. 在策略模式中,Startegy 和 Context 的接口都是两者之间的协作接口,并不涉及到其它的功能接口,所以它是行为模式的一种。行为模式的主要特点就是处理的是对象之间的通信方式,往往是通过引入中介者对象将通信双方解耦,在这里实际上就是将 Context 与实际的算法提供者解耦。属于行为型模式,目的可以理解为低耦合
    %%
    0808-🏡⭐️◼️设计模式的目的:结构上高内聚,行为上低耦合◼️⭐️-point-202301250808%%

8.2.3. 范畴不同

  1. 相对与策略模式,桥接模式要表达的内容要更多,结构也更加复杂。桥接模式表达的主要意义其实是接口隔离的原则,即把本质上并不内聚的两种体系区别开来,使得它们可以松散的组合,而策略在解耦上还仅仅是某一个算法的层次,没有到体系这一层次。
  2. 从结构图中可以看到,策略的结构是包容在桥接结构中的,桥接中必然存在着策略模式,Abstraction 与 Implementor 之间就可以认为是策略模式,但是桥接模式一般 Implementor 将提供一系列的成体系的操作,而且 Implementor 是具有状态和数据的静态结构。而且桥接模式 Abstraction 也可以独立变化。

8.2.4. 逻辑不同

聚合其他接口的类的子类,通过构造导入的方式,可以调用接口实现类的方法

9. 策略模式与状态模式的区别

  1. 策略模式有多个抽象策略类,而状态模式一般只有一个抽象状态类
  2. 策略模式不同策略之间不存在流转等其他关系,而状态模式不同状态之间需要流转

10. 实战经验⭐️🔴

  • 1)策略模式的关键是:分析项目中变化部分与不变部分
  • 2)策略模式的核心思想是:多用组合/聚合,少用继承;用行为类组合,而不是行为的继承,更有弹性
    %%
    01230729-🏡⭐️◼️涉及动态替换的模式:策略模式、装饰者模式、责任链模式。涉及变化与不变的模式:策略模式(算法不同)、模板方法模式(流程和细节都有不同)、建造者模式(流程相同,细节不同)、享元模式(内部状态相同,外部状态不同)◼️⭐️-point-202301230729%%

11. 参考与感谢

设计模式-2、设计模式及设计原则