策略模式
# 策略模式
在现实生活中常常遇到实现某种目标可以选择多种策略的情况,例如,出行方式可以选择公交、地铁、自行车、步行或开车等,超市促销可以采用打折、满送、满减等方法。
在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据不同条件选择不同的算法或者策略来完成该功能。
如果使用条件语句实现,不但使条件语句变得很复杂,而且不易维护,违背开闭原则。如果采用策略模式就能很好的解决该问题。
# 定义
策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式与状态模式的区别
策略模式(Strategy Pattern)与状态模式(State Pattern)的主要区别在于它们关注的问题领域和解决的问题类型。策略模式关注选择和切换不同的算法或策略,状态模式关注对象内部的状态变化。
策略模式关注选择和切换不同的算法或策略,它通过封装算法或策略为独立类来实现策略的选择和切换。而状态模式关注对象的内部状态的变化和行为的变化,它通过封装状态为独立类来实现状态之间的切换。
策略模式与访问者模式的区别
策略模式用于在运行时选择算法或策略,将算法封装在策略类中。访问者模式通过访问者对象用于对元素进行不同的操作,将操作封装在访问者类中。
# 结构
策略接口(Strategy Interface): 策略接口定义了所有具体策略类(算法类)所需实现的方法。它通常是一个接口或抽象类,声明了算法的公共操作。
具体策略类(Concrete Strategies): 具体策略类实现了策略接口,提供了具体的算法实现。每个具体策略类都封装了一种特定的算法,并提供了算法的具体实现。
环境类(Context): 环境类持有一个策略接口的引用,并在运行时根据需要调用具体策略类的算法。环境类将客户端与具体策略类解耦,客户端只需要与环境类进行交互,而无需直接与具体策略类交互。
- img: https://bitouyun.com/images/design-pattern/strategy2.png
link: https://bitouyun.com/images/design-pattern/strategy2.png
name: 策略模式
2
3
# 优点
易于扩展和维护:由于策略模式将每个算法封装成独立的策略类,因此添加新的策略只需要新增一个具体策略类,符合开闭原则。同时,修改现有的策略也只需要修改相应的具体策略类,不会对其他策略产生影响,降低了代码的耦合性,使得系统更易于扩展和维护。
灵活性高:策略模式通过将算法封装成独立的策略类,使得这些策略可以在运行时动态替换,客户端可以根据需要选择不同的策略,提供了更高的灵活性。这样,系统可以根据具体情况选择最合适的算法,实现定制化的行为。
可复用性好:策略模式将算法封装成独立的策略类,这些策略类可以被多个客户端共享和复用。不同的客户端可以使用相同的策略类,避免了代码的重复编写,提高了代码的复用性。
降低算法使用与算法实现之间的耦合度:策略模式通过环境类来与具体策略类进行交互,客户端只需要与环境类进行交互,而无需直接与具体策略类交互。这样,策略的选择和使用与具体策略类的实现相互解耦,降低了客户端与具体策略类之间的耦合度。
# 缺点
增加了类的数量:使用策略模式会引入多个策略类,可能会增加系统中的类的数量。如果策略较多,可能会导致类的数量增加过多,增加了代码的复杂性和理解难度。
客户端需要了解不同的策略:客户端需要了解不同的策略类,以便在运行时选择合适的策略。这要求客户端具有一定的了解和判断能力,增加了客户端的复杂性。客户端选择了不合适的策略,可能会导致系统的性能或行为问题。
# 应用场景
一个系统需要根据不同场景,切换不同的实现逻辑,可将每个算法封装到策略类中;
系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时;
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为;
存在大量if else条件判断。
# 示例代码1
提示
上下文角色(环境类)中如果策略数量小可以使用switch条件分支切换策略,策略数量多可以从IOC容器中获取策略对象。
SpringBoot下要从容器中获取对象实例。
场景1
超市促销活动:正常收费、打折促销、满减活动,根据不同的活动计算收费金额。客户端只需跟环境类交互,环境类中添加工厂模式,根据传入不同的策略枚举值创建具体的策略对象,计算对应的结果。
/**
* 上下文角色(环境类)
*/
public class CashContext {
// 现金抽象类(策略接口)
private CashSuper cashSuper;
/**
* 构造方法传入价格策略
*
* @param cashSuper
*/
public CashContext(CashSuper cashSuper) {
this.cashSuper = cashSuper;
}
/**
* 根据收费类型生成具体收费策略
* 策略与简单工程结合
*
* @param type 0:正常收费 1:满减收费 2:打折收费
*/
public CashContext(String type) {
switch (type) {
case "0":
CashNormal cashNormal = new CashNormal(); // springboot下要从容器中获取对象实例
// CashNormal cashNormal = (CashNormal) applicationContext.getBean("cashNormal");
cashSuper = cashNormal;
break;
case "1":
CashReturn cashReturn = new CashReturn(300, 100);
cashSuper = cashReturn;
break;
case "2":
CashRebate cashRebate = new CashRebate(0.8);
cashSuper = cashRebate;
break;
}
}
/**
* 获取实际收费金额
*
* @param money 原价
* @return 实际收取金额
*/
public double getResult(double money) {
return cashSuper.acceptCash(money);
}
}
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
48
49
50
/**
* 现金收费抽象类
* 策略接口,定义了所有策略类(算法类)需要实现的方法,通常是一个接口或抽象类,声明所有算法的公共操作。
*/
public abstract class CashSuper {
/**
* 收取现金
*
* @param money 原价
* @return 实际收取金额
*/
public abstract double acceptCash(double money);
}
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 正常收费类
* 具体策略类
*/
public class CashNormal extends CashSuper {
@Override
public double acceptCash(double money) {
return money;
}
}
2
3
4
5
6
7
8
9
10
11
12
/**
* 折扣收费类
* 具体策略类
*/
public class CashRebate extends CashSuper {
// 折扣
private double moneyRebate;
public CashRebate(double moneyRebate) {
this.moneyRebate = moneyRebate;
}
@Override
public double acceptCash(double money) {
return money * moneyRebate;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 返利收费类
* 具体策略类
* 满300减100
*/
public class CashReturn extends CashSuper {
// 返利条件 ex:300
private double moneyCondition;
public CashReturn(double moneyCondition, double moneyReturn) {
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
// 返利金额 ex:100
private double moneyReturn;
@Override
public double acceptCash(double money) {
double result = money;
if (money > moneyCondition) {
// 每满300-100 结果 = 金额 - 返利条件倍数 * 返利金额
result = money - Math.floor(money / moneyCondition) * moneyReturn;
}
return result;
}
}
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
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
double total = 500.0;
// 创建上下文角色(环境类Context)
CashContext cashNormal = new CashContext("0");
double normalResult = cashNormal.getResult(total);
System.out.println("正常收费:" + normalResult);
CashContext cashContextReturn = new CashContext("1");
double cashReturnResult = cashContextReturn.getResult(total);
System.out.println("返现收费:" + cashReturnResult);
CashContext cashRebate = new CashContext("2");
double rebateResult = cashRebate.getResult(total);
System.out.println("折扣收费:" + rebateResult);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Make sure to add code blocks to your code group
# 示例代码2
场景2
投资调查问卷,需要统计问卷的总分和每个维度的平均分,抽象为计算原始总分和计算平均分两种策略,使用时根据传入的枚举值决定具体使用哪种策略进行计算。
/**
* 上下文角色(环境类)
*/
@Slf4j
@Service
public class CalcScoreContext {
@Resource
private ApplicationContext applicationContext;
/**
* 计算分数
* @param calculateTypeEnum 类型
* @return 计算结果
*/
public CalculateResult getScore(CalcTypeEnum calculateTypeEnum) {
AbstractCalcScore abstractCalculateScore = (AbstractCalcScore) applicationContext.getBean(calculateTypeEnum.getBean());
// 计算分数
return abstractCalculateScore.calculate();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 抽象策略类
* 定义了所有具体策略类(算法类)所需实现的方法
*/
public abstract class AbstractCalcScore {
/**
* 计算分数
* @return 分数
*/
abstract CalculateResult calculate();
}
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 具体策略类,计算原始总分
*/
@Slf4j
@Service
public class OriginalScore extends AbstractCalcScore {
public CalculateResult calculate() {
log.debug("计算原始分");
return new CalculateResult(90.0);
}
}
2
3
4
5
6
7
8
9
10
11
12
/**
* 具体策略类,计算平均分
*/
@Slf4j
@Service
public class AverageScore extends AbstractCalcScore {
@Override
CalculateResult calculate() {
log.debug("计算平均分", calcCondition);
return new CalculateResult(80.0);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
@Data
@AllArgsConstructor
public class CalculateResult implements Serializable {
private Double score;
}
2
3
4
5
@Getter
@AllArgsConstructor
public enum CalcTypeEnum {
ORIGINAL(1, "原始分", "originalScore"),
AVERAGE(2, "平均分", "averageScore");
private Integer type;
private String name;
private String bean;
public static CalcTypeEnum fromType(Integer type) {
for (CalcTypeEnum calculateTypeEnum : CalcTypeEnum.values()) {
if (calculateTypeEnum.type.equals(type)) {
return calculateTypeEnum;
}
}
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j
@Service
public class CalculateService{
// 上下文角色(环境类)
@Resource
private CalcScoreContext calcScoreContext;
// 客户端调用方法时,传入对应计算枚举
@Override
public void execCalculate(CalcTypeEnum calcTypeEnum) {
// 计算分数
CalculateResult calculateResult = this.calcScoreContext.getScore(calcTypeEnum);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
// Make sure to add code blocks to your code group