系列文章目录
总目录链接
文章目录
设计模式-策略模式-Strategy Pattern
Overview
- 策略模式(Strategy Pattern)是一种行为型设计模式
- 它定义了一系列的算法,并将每一个算法封装起来,使它们可以互换使用,算法的变化不会影响使用算法的用户
- 策略模式让算法独立于使用它的客户端而变化。
1.策略模式(Strategy Pattern)
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以互换使用,算法的变化不会影响使用算法的用户。策略模式让算法独立于使用它的客户端而变化。
在C++中实现策略模式通常涉及以下几个步骤:
- 定义策略接口:这个接口定义了所有支持的算法必须遵循的方法。
- 实现具体策略:为每一个算法创建一个类,实现策略接口。
- 创建上下文:上下文持有策略接口的引用,通过这个引用调用算法的方法。
以下是一个简单的C++实现示例:
#include <iostream>
#include <vector>
#include <memory>
// 策略接口
class Strategy {
public:
virtual ~Strategy() {}
virtual int execute(int n) = 0;
};
// 具体策略A
class ConcreteStrategyA : public Strategy {
public:
int execute(int n) override {
return n * n;
}
};
// 具体策略B
class ConcreteStrategyB : public Strategy {
public:
int execute(int n) override {
return n * n * n;
}
};
// 上下文
class Context {
private:
std::unique_ptr<Strategy> strategy;
public:
void setStrategy(std::unique_ptr<Strategy> s) {
strategy = std::move(s);
}
int executeStrategy(int n) {
if (strategy) {
return strategy->execute(n);
}
return 0;
}
};
int main() {
Context context;
// 使用策略A
std::unique_ptr<Strategy> strategyA(new ConcreteStrategyA());
context.setStrategy(std::move(strategyA));
std::cout << "Strategy A (10): " << context.executeStrategy(10) << std::endl;
// 更换策略为B
std::unique_ptr<Strategy> strategyB(new ConcreteStrategyB());
context.setStrategy(std::move(strategyB));
std::cout << "Strategy B (10): " << context.executeStrategy(10) << std::endl;
return 0;
}
在这个例子中,Strategy
是策略接口,它定义了一个 execute
方法。ConcreteStrategyA
和 ConcreteStrategyB
是具体策略类,它们实现了 Strategy
接口,并提供了不同的算法实现。
Context
是上下文类,它持有一个 Strategy
接口的指针,并使用它来执行算法。在 main
函数中,我们创建了一个 Context
对象,并分别设置了 ConcreteStrategyA
和 ConcreteStrategyB
策略,然后执行了算法。
策略模式的优点包括算法的封装和切换的灵活性,以及算法可独立于使用它的客户端变化。缺点可能包括策略类的数量增多,以及需要维护这些策略类。在设计时,应该根据实际需求来决定是否使用策略模式。
2.策略模式优缺点
- 优点
- 你可以在运行时切换对象内的算法。
- 你可以将算法的实现和使用算法的代码隔离开来。
- 你可以使用组合来代替继承。
- 开闭原则。 你无需对上下文进行修改就能够引入新的策略。
- 缺点
- 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。
- 客户端必须知晓策略间的不同——它需要选择合适的策略。
- 许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法。 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。
策略模式(Strategy Pattern)的优缺点如下:
2.1.优点
-
算法的封装:策略模式将算法封装在独立的策略类中,使得算法可以独立于使用它的客户端而变化。
-
易于扩展:新的策略可以很容易地添加到系统中,而不需要修改现有的代码,符合开闭原则。
-
消除条件语句:策略模式可以消除复杂的条件分支语句,使得代码更加简洁和易于维护。
-
算法的互换性:策略模式允许在运行时切换不同的算法,使得算法可以像对象一样被创建和销毁。
-
提高代码的可读性:通过将算法封装在独立的类中,策略模式使得代码结构更加清晰,易于理解。
-
算法的重用:策略模式使得算法可以在不同的上下文中重用,提高了代码的复用性。
-
灵活性:策略模式提供了高度的灵活性,允许在不同的时间点选择不同的策略。
2.2.缺点
-
客户端必须了解策略:客户端需要了解所有可用的策略,以便能够做出正确的选择。
-
策略类的数量增多:如果系统中有大量的策略,可能会导致策略类的数目迅速增长,增加系统的复杂性。
-
可能增加内存开销:策略模式可能会增加内存的使用,因为每个策略都是一个对象,需要为其分配内存。
-
可能引入复杂性:在某些简单的情况下,策略模式可能会引入不必要的复杂性。
-
可能违反里氏替换原则:如果策略类的实现不完全遵循基类或接口的约定,可能会导致里氏替换原则被违反。
-
可能影响性能:策略模式可能会影响性能,因为每次切换策略都需要创建新的策略对象,这可能会增加内存分配和对象创建的开销。
-
策略模式与简单工厂模式的混淆:在某些情况下,策略模式可能会与简单工厂模式混淆,导致设计上的混淆。
在使用策略模式时,应该根据具体的应用场景和需求来决定是否采用这种模式。在设计时,应该考虑到策略模式可能带来的复杂性,并确保策略的切换逻辑清晰且易于管理。
3.策略模式在实际开发中有哪些常见的应用场景?
策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列的算法,并将每一个算法封装起来,使它们可以互换使用。策略模式让算法的变化独立于使用算法的客户。在软件开发中,策略模式有多种应用场景,以下是一些常见的例子:
-
支付方式选择:在电子商务平台中,用户可以选择不同的支付方式,如信用卡、PayPal、微信支付等。每种支付方式都是一个策略,可以根据用户的选择动态切换。
-
排序算法:在需要排序数据时,可能会有多种排序算法,如快速排序、归并排序、冒泡排序等。策略模式允许用户根据需要选择最合适的排序算法。
-
图形渲染:在图形处理软件中,不同的图形对象可能需要不同的渲染策略,例如,2D图形和3D图形的渲染方式不同。
-
数据访问:在多层架构中,数据访问层可能会使用不同的数据源或访问技术(如数据库、文件系统、远程服务等),策略模式可以用来封装这些不同的访问方式。
-
缓存策略:在需要缓存数据的系统中,可能会有多种缓存策略,如LRU(最近最少使用)、FIFO(先进先出)等,策略模式可以用来实现这些不同的缓存策略。
-
日志记录:在应用程序中,可能会有多种日志记录方式,如控制台日志、文件日志、数据库日志等。策略模式可以用来根据不同的需要选择不同的日志记录方式。
-
用户权限验证:在需要权限控制的系统中,可能会有多种权限验证策略,如基于角色的访问控制(RBAC)、基于属性的访问控制(ABAC)等。
-
算法动态切换:在需要根据不同条件动态切换算法的场景中,策略模式可以提供灵活的算法切换机制,例如,根据输入数据的特性选择不同的算法。
-
游戏AI行为:在游戏开发中,不同的角色可能会有不同的行为策略,如攻击、防御、逃跑等,策略模式可以用来实现这些不同的行为。
-
配置文件解析:在需要解析不同格式配置文件的系统中,如XML、JSON、YAML等,策略模式可以用来封装不同的解析算法。
策略模式的关键在于它提供了一种方式,使得算法可以在运行时被选择和更换,而不需要修改使用算法的代码。这增加了代码的灵活性和可维护性。
4.在C++中,如何优化策略模式以提高性能?
在C++中优化策略模式以提高性能,可以考虑以下几个方面:
-
减少动态内存分配:频繁的内存分配和释放会影响性能。可以通过使用对象池(Object Pool)来重用对象,减少动态内存分配的次数。
-
利用移动语义:C++11引入了移动语义,它允许资源从一个对象转移到另一个对象,而不需要复制。这可以减少不必要的拷贝操作,提高性能。
-
返回值优化(RVO):编译器可以优化函数返回局部对象时的拷贝操作。确保你的代码能够利用编译器的这种优化。
-
使用引用传递:对于大型对象或频繁传递的对象,使用引用传递而不是值传递,可以避免拷贝操作。
-
避免不必要的虚函数调用:虚函数调用有一定的开销。如果策略模式中的算法可以是确定的,考虑使用非虚函数或静态多态性(如函数指针或std::function)。
-
选择适当的数据结构:使用高效的数据结构可以减少算法的复杂度。例如,使用
std::vector
而不是std::list
,因为std::vector
在随机访问时更高效。 -
预先分配内存:如果你知道将要使用的资源大小,预先分配足够的内存可以避免在运行时多次分配和释放。
-
减少虚函数表查找:虚函数表(v-table)查找会增加额外的开销。在策略模式中,如果可能,使用接口继承来减少虚函数的数量。
-
使用constexpr和编译期计算:对于不会改变的策略参数,使用
constexpr
可以在编译期进行计算,减少运行时的计算负担。 -
代码剖析:使用剖析工具(如gprof)来确定性能瓶颈,并针对这些瓶颈进行优化。
通过这些策略,你可以在保持策略模式灵活性的同时,提高程序的性能。记住,优化应该基于实际的性能数据,而不是盲目地进行。在进行优化之前,最好先进行性能剖析,找出真正的瓶颈所在。
5.在实际开发中,如何判断一个场景是否适合使用策略模式?
在实际开发中,判断一个场景是否适合使用策略模式,可以依据以下几个关键点:
-
算法的多样性:如果系统中存在多种算法或行为,并且这些算法在功能上相似,但实现方式不同,策略模式可能是一个合适的选择。
-
算法的可替换性:如果算法可能会在运行时根据条件变化,或者用户需要能够选择不同的算法,策略模式提供了一种灵活的方式来切换算法。
-
算法的独立性:如果算法的实现细节对于客户端来说是透明的,或者客户端不需要知道算法的具体实现,策略模式可以帮助封装算法的细节。
-
算法的复杂性:如果算法的实现非常复杂,或者算法可能会随着时间而变化,策略模式可以帮助管理这种复杂性,使得算法的变化不会影响到使用算法的代码。
-
客户端的多样性:如果不同的客户端需要不同的算法,或者客户端需要能够根据需要选择不同的算法,策略模式可以提供这种灵活性。
-
扩展性:如果预计未来可能会添加更多的算法,策略模式可以使得添加新算法变得更加容易,而不需要修改现有的代码。
-
代码的可维护性:如果算法的实现分散在多个地方,或者算法的实现与使用算法的代码紧密耦合,策略模式可以帮助提高代码的可维护性。
-
性能考虑:如果算法的性能是一个关键因素,策略模式可以帮助选择最合适的算法,或者在不同的算法之间进行权衡。
-
配置的灵活性:如果算法的选择需要通过配置文件或用户输入来决定,策略模式可以使得这种配置变得更加灵活。
-
解耦合:如果希望算法的实现与使用算法的代码解耦,策略模式可以帮助实现这一点。
在考虑使用策略模式时,还可以问自己以下问题:
- 是否有多种算法可以解决同一问题?
- 是否需要在运行时选择或切换算法?
- 是否希望算法的实现对客户端透明?
- 是否希望算法的添加、修改或删除对现有代码的影响最小?
如果对这些问题的回答大多数是肯定的,那么策略模式可能是一个合适的选择。
6.策略模式在设计时有哪些常见的错误或者陷阱需要避免?
在设计策略模式时,确实存在一些常见的错误或陷阱,以下是一些需要避免的情况:
-
过度使用:策略模式并不是解决所有问题的万能钥匙。如果只有一种算法或者算法的选择不经常变化,使用策略模式可能会导致不必要的复杂性。
-
策略类与上下文类耦合:策略模式的目的是将算法的实现与使用算法的代码解耦。如果策略类与上下文类(Context)耦合得太紧密,就会失去策略模式的优势。
-
策略接口设计不当:策略接口应该定义清晰且一致的方法,以便不同的策略可以互换。如果接口设计得过于复杂或不一致,可能会导致策略难以实现或替换。
-
策略类太多:如果为每种可能的情况都创建一个策略类,可能会导致类的数量过多,增加维护成本。应该考虑是否有一些策略可以合并或者是否有一些策略可以通过参数配置来实现。
-
策略选择逻辑复杂:策略的选择逻辑应该简单明了。如果策略选择逻辑过于复杂,可能会使得代码难以理解和维护。
-
忽视策略的初始化:策略对象可能需要一些初始化数据才能正确工作。如果忽视了这一点,可能会导致策略对象在运行时出现错误。
-
忽视策略的销毁:如果策略对象持有资源(如数据库连接、文件句柄等),则需要确保这些资源在策略对象不再使用时被正确释放。
-
策略类的性能问题:策略类的性能可能会影响整个系统的性能。在设计策略时,应该考虑策略的性能,并在必要时进行优化。
-
策略类的状态管理:如果策略类持有状态,需要确保状态在策略切换时被正确处理。否则,可能会导致不一致的状态或错误。
-
策略类的错误处理:策略类应该能够处理错误,并在出现错误时提供清晰的反馈。如果忽视了错误处理,可能会导致系统在运行时出现不可预测的行为。
-
策略类的线程安全性:如果策略对象在多线程环境中使用,需要确保策略对象是线程安全的,或者提供适当的同步机制。
-
忽视策略的可测试性:策略模式应该使得策略类易于测试。如果策略类难以测试,可能会影响代码的质量和可维护性。
在设计策略模式时,应该仔细考虑这些潜在的问题,并采取适当的措施来避免它们。通过合理的设计和实现,策略模式可以提高代码的灵活性、可维护性和可扩展性。
关于作者
- 微信公众号:WeSiGJ
- GitHub:https://github.com/wesigj/cplusplusboys
- CSDN:https://blog.csdn.net/wesigj
- 微博:
- -版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。