【再谈设计模式】策略模式 ~ 算法与行为的灵活调度员
本章内容思维导图:
一、引言
在软件工程,软件开发过程中,我们常常会遇到这样的情况:需要根据不同的条件或者用户输入来执行不同的算法或者操作流程。例如,在一个电商系统中,根据用户的会员等级,可能需要计算不同的折扣;在图形绘制系统中,根据选择的图形类型(圆形、矩形、三角形等)执行不同的绘制算法。如果使用大量的if - else或者switch - case语句来处理这些不同的情况,代码会变得非常臃肿、难以维护和扩展。
这时候,策略模式就可以派上用场了。
二、定义与描述
策略模式属于行为设计模式的一种。它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
三、抽象背景
从抽象的角度来看,策略模式旨在将可变的行为从一个类中分离出来,将其封装成独立的策略类。这样一来,原本依赖于具体行为的类就可以依赖于抽象的策略接口,从而在运行时能够轻松切换不同的策略实现,而不需要对依赖该行为的类进行大量修改。
四、适用场景与现实问题解决
- 多种算法选择场景
- 在排序算法中,如果有冒泡排序、快速排序、归并排序等多种算法可供选择,就可以使用策略模式。用户可以根据数据的特点(如数据量大小、数据是否基本有序等)在运行时选择合适的排序策略。
- 业务规则频繁变化场景
- 以电商系统中的促销策略为例。促销策略可能包括满减、折扣、赠品等多种形式,并且这些策略可能会随着市场情况不断变化。使用策略模式可以方便地添加、修改或删除促销策略,而不会对整个电商系统的订单处理逻辑造成太大的干扰。
五、策略模式在现实生活中的例子
- 出行方式选择
- 我们出行时,有多种出行方式可供选择,如步行、骑自行车、乘坐公交车、开车等。每种出行方式都可以看作是一种策略。我们可以根据距离远近、天气情况、时间紧迫性等因素选择不同的出行策略。在程序中,可以定义一个抽象的“出行方式”接口,然后分别实现“步行策略”、“骑自行车策略”、“乘坐公交车策略”和“开车策略”等具体策略类。
六、初衷与问题解决
- 初衷
- 策略模式的初衷是为了提高代码的灵活性和可维护性。通过将算法或行为封装成独立的策略类,使得代码的结构更加清晰,不同的策略可以独立开发、测试和维护。
- 问题解决
- 它解决了在复杂业务逻辑中,大量条件判断导致的代码可读性差、难以扩展和维护的问题。当需要添加新的策略时,只需要创建一个新的策略类并实现相应的策略接口,而不需要在大量的条件判断语句中进行修改。
七、代码示例
(一)Java示例
// 策略接口
interface Strategy {
int doOperation(int num1, int num2);
}
// 具体策略类:加法策略
class ConcreteStrategyAdd implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
// 具体策略类:减法策略
class ConcreteStrategySubtract implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
// 上下文类
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2) {
return strategy.doOperation(num1, num2);
}
}
使用示例:
public class Main {
public static void main(String[] args) {
Context context = new Context(new ConcreteStrategyAdd());
int result = context.executeStrategy(5, 3);
System.out.println("使用加法策略结果: " + result);
context = new Context(new ConcreteStrategySubtract());
result = context.executeStrategy(5, 3);
System.out.println("使用减法策略结果: " + result);
}
}
类图:
- 首先定义了
Strategy
接口,声明了doOperation
方法,接着定义了实现该接口的两个具体策略类ConcreteStrategyAdd
和ConcreteStrategySubtract
,它们都重写了doOperation
方法。 - 然后定义了
Context
类,它包含一个Strategy
类型的私有成员变量,并且有构造函数用于传入策略对象以及executeStrategy
方法用于执行对应策略的操作。 - 最后通过
--|>
表示继承关系(具体策略类继承接口),通过* - "1"
表示关联关系(Context
类与Strategy
存在关联,一个Context
对象关联一个Strategy
对象)。
时序图:
Client
(客户端)作为发起操作的角色,首先创建Context
实例并传入不同的具体策略类(如ConcreteStrategyAdd
或ConcreteStrategySubtract
)实例。- 然后客户端调用
Context
类的executeStrategy
方法,Context
类再根据其持有的具体策略对象引用去调用相应策略类的doOperation
方法来执行具体运算,最后将结果返回给客户端。
使用流程图:
使用加法策略和减法策略的完整流程步骤,先是创建具体策略类实例,将其传入Context
类构建上下文对象,接着准备参数,然后调用相应方法执行运算并最终返回结果。你可以把这段 PlantUML 代码放到支持 PlantUML 的工具(比如在线的 PlantUML 编辑器等)里,查看生成的可视化的流程图,更直观地理解整个操作流程逻辑。
(二)C++示例
// 策略抽象类
class Strategy {
public:
virtual int doOperation(int num1, int num2) = 0;
};
// 具体策略类:加法策略
class ConcreteStrategyAdd : public Strategy {
public:
int doOperation(int num1, int num2) override {
return num1 + num2;
}
};
// 具体策略类:减法策略
class ConcreteStrategySubtract : public Strategy {
public:
int doOperation(int num1, int num2) override {
return num1 - num2;
}
};
// 上下文类
class Context {
private:
Strategy* strategy;
public:
Context(Strategy* strategy) : strategy(strategy) {}
int executeStrategy(int num1, int num2) {
return strategy->doOperation(num1, num2);
}
};
使用示例:
int main() {
Context* context = new Context(new ConcreteStrategyAdd());
int result = context->executeStrategy(5, 3);
std::cout << "使用加法策略结果: " << result << std::endl;
context = new Context(new ConcreteStrategySubtract());
result = context->executeStrategy(5, 3);
std::cout << "使用减法策略结果: " << result << std::endl;
return 0;
}
(三)Python示例
# 策略抽象类
from abc import ABC, abstractmethod
class Strategy(ABC):
@abstractmethod
def doOperation(self, num1, num2):
pass
# 具体策略类:加法策略
class ConcreteStrategyAdd(Strategy):
def doOperation(self, num1, num2):
return num1 + num2
# 具体策略类:减法策略
class ConcreteStrategySubtract(Strategy):
def doOperation(self, num1, num2):
return num1 - num2
# 上下文类
class Context:
def __init__(self, strategy):
self.strategy = strategy
def executeStrategy(self, num1, num2):
return self.strategy.doOperation(num1, num2)
使用示例:
context = Context(ConcreteStrategyAdd())
result = context.executeStrategy(5, 3)
print("使用加法策略结果:", result)
context = Context(ConcreteStrategySubtract())
result = context.executeStrategy(5, 3)
print("使用减法策略结果:", result)
(四)Go示例
// 策略接口
type Strategy interface {
doOperation(num1, num2 int) int
}
// 具体策略类:加法策略
type ConcreteStrategyAdd struct{}
func (c ConcreteStrategyAdd) doOperation(num1, num2 int) int {
return num1 + num2
}
// 具体策略类:减法策略
type ConcreteStrategySubtract struct{}
func (c ConcreteStrategySubtract) doOperation(num1, num2 int) int {
return num1 - num2
}
// 上下文类
type Context struct {
strategy Strategy
}
func (c *Context) executeStrategy(num1, num2 int) int {
return c.strategy.doOperation(num1, num2)
}
使用示例:
func main() {
context := &Context{strategy: ConcreteStrategyAdd{}}
result := context.executeStrategy(5, 3)
println("使用加法策略结果:", result)
context = &Context{strategy: ConcreteStrategySubtract{}}
result = context.executeStrategy(5, 3)
println("使用减法策略结果:", result)
}
八、策略模式的优缺点
(一)优点
- 提高可维护性
- 由于每个策略都是一个独立的类,所以当需要修改某个策略时,只需要在相应的策略类中进行修改,不会影响到其他代码。
- 增强代码的灵活性
- 可以在运行时动态地切换策略,根据不同的需求选择不同的算法或行为。
- 符合开闭原则
- 当需要添加新的策略时,只需要创建一个新的策略类并实现策略接口,而不需要修改现有的代码。
(二)缺点
- 增加类的数量
- 每一个策略都需要一个单独的类,对于简单的算法,可能会导致类的数量过多,增加了代码的复杂性。
- 客户端必须了解策略间的区别
- 客户端需要知道每个策略的具体功能,以便能够正确地选择和使用策略。
九、策略模式的升级版
- 策略模式与工厂模式结合
- 可以将策略模式与工厂模式相结合。工厂模式负责创建策略对象,这样可以将策略对象的创建和使用分离得更加彻底。例如,在一个游戏系统中,根据不同的游戏场景创建不同的游戏策略(如攻击策略、防御策略等),可以使用工厂模式来创建这些策略对象,然后在游戏角色中使用这些策略对象。
- 策略模式与状态模式的融合
- 在一些情况下,策略模式和状态模式可以融合使用。当一个对象的状态变化会导致其行为发生变化,并且这些行为可以用策略模式来表示时,可以将两者结合。例如,在一个订单处理系统中,订单的状态(未支付、已支付、已发货等)不同,处理订单的策略(如计算价格、安排发货等)也不同。通过将状态模式和策略模式结合,可以更好地处理这种复杂的业务逻辑。
原文地址:https://blog.csdn.net/wnm23/article/details/144783327
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!