设计模式
工厂模式 : 设计模式之工厂模式-CSDN博客
迭代器模式:设计模式之迭代器模式-CSDN博客
适配器模式:设计模式之适配器模式-CSDN博客
过滤器模式:设计模式之过滤器模式-CSDN博客
观察者模式:设计模式之观察者模式-CSDN博客
空对象模式:设计模式之空对象模式-CSDN博客
桥接模式:设计模式之桥接模式-CSDN博客
责任链模式:设计模式之责任链模式-CSDN博客
策略模式:设计模式之策略模式-CSDN博客
Pimpl技法:C++之Pimpl惯用法-CSDN博客
组合模式:设计模式之组合模式-CSDN博客
单例模式:设计模式之单例模式-CSDN博客
目录
1.简介
策略模式属于行为类设计模式;在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。
在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。也是说这些算法所完成的功能类型是一样的,对外接口也是一样的,只是不同的策略为引起环境角色(持有一个策略类的引用,最终给客户端调用)表现出不同的行为。
相比于使用大量的if...else,使用策略模式可以降低复杂度,使得代码更容易维护。在我之前的博客中优化多层if-else-if就用到了策略模式。
2.结构
通用UML类图如下图所示:
策略模式包含以下几个核心角色:
环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
抽象策略(Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。如上述图中ConcreteStategyA、ConcreteStategyB、ConcreteStategyC。
3.实现
3.1.动态策略
以通信行业的数据加密为例,我们平时的语音、文字、图像等数据信息,它们通过无线或有线传输到对端,如果是无线传输就需要编码、加密等手段,那么加密的方式是不是就相当于策略,于是我们可以先定义策略基类:
class IDataCodec
{
public:
virtual ~IDataCodec() {}
virtual int encodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) = 0;
virtual int decodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) = 0;
};
假如加密手段有CRC、汉明编码、AES等,于是就在IDataCodec的基础上具体实现编解码,代码如下:
//CRC编码
class CCrcParityCodec : public IDataCodec
{
public:
explicit CCrcParityCodec(bool isMaster);
CCrcParityCodec(const CCrcParityCodec& src){
this->m_bMaster = src.m_bMaster;
}
virtual ~CCrcParityCodec();
public:
int encodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) override{
//...
return 1;
}
int decodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) override {
//...
return 1;
}
private:
bool m_bMaster;
};
//汉明编码
class CHmCodec : public IDataCodec
{
public:
explicit CHmCodec();
virtual ~CHmCodec();
public:
int encodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) override{
//...
return 1;
}
int decodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) override{
//...
return 1;
}
};
//AES
class CAesCodec : public IDataCodec
{
public:
explicit CAesCodec();
virtual ~CAesCodec();
public:
int encodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) override{
//...
return 1;
}
int decodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) override{
//...
return 1;
}
};
//不需要编码的类
class CNoUseDataCodec : public IDataCodec
{
public:
virtual ~CNoUseDataCodec() {}
int encodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) override{
pDestData = const_cast<char*>(pSrcData);
nDestLen = nSrcLen;
return 1;
}
int decodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) override{
pDestData = const_cast<char*>(pSrcData);
nDestLen = nSrcLen;
return 1;
}
};
上下文定义为:
class CDataContext
{
public:
CDataContext() : m_pDataCodec(&m_noUseDataCodec) {}
void setDataCodec(IDataCodec* pCodec) { m_pDataCodec = pCodec; }
int encodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) {
return m_pDataCodec->encodeData(pSrcData, nSrcLen, pDestData,nDestLen);
}
int decodeData(const char* pSrcData, int nSrcLen, char* pDestData, int& nDestLen) {
return m_pDataCodec->decodeData(pSrcData, nSrcLen, pDestData,nDestLen);
}
private:
IDataCodec* m_pDataCodec;
static CNoUseDataCodec m_noUseDataCodec;
};
CNoUseDataCodec CDataContext::m_noUseDataCodec;
测试例子:
int main()
{
CDataContext context;
std::unique_ptr<IDataCodec> pCRCCodec(new CCrcParityCodec());
std::unique_ptr<IDataCodec> pCRCCodec(new CHmCodec());
std::unique_ptr<IDataCodec> pCRCCodec(new CAesCodec());
context.setDataCodec(pCRCCodec.get());
context->encodeData(...);
context.setDataCodec(pCRCCodec.get());
context->encodeData(...);
context.setDataCodec(pCRCCodec.get());
context->encodeData(...);
}
3.2.函数式策略
以排序为例:std::sort,它的实现代码如下:
template <class _RanIt, class _Pr>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred) { // order [_First, _Last)
_Adl_verify_range(_First, _Last);
const auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
_Sort_unchecked(_UFirst, _ULast, _ULast - _UFirst, _Pass_fn(_Pred));
}
最后一个参数_Pr就是排序的策略,是按照大小还是按照名称还是其它等等,这些都是具体的排序策略,默认的排序策略就是std::less<>
template <class _RanIt>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last) { // order [_First, _Last)
_STD sort(_First, _Last, less<>{});
}
我们也可以自定义策略,如:
vector<int> value{34, 3,6,10,7,1};
//默认策略
std::sort(value.begin(), value.end());
//全局函数策略
int compare(int x, int y){ return x > y;}
std::sort(value.begin(), value.end(), compare);
//匿名表达式策略
std::sort(value.begin(), value.end(), [](int x, int y){ return x > y; });
//仿函数策略
struct MyFunctor{
int compare(int x, int y) {return x > y;}
};
std::sort(value.begin(), value.end(), MyFunctor());
3.3.静态策略(policy-based class)
Policy-based 是 C++ 的一种基于模板和继承的设计方法。通过模板方式可以实现在编译期给指定的类配置方法,而传统的策略模式则是在运行期对类的方法进行动态配置。
Police-based class机制是由template和多重继承组成,一个class如果使用了policies,我们称其为host class,是一个拥有多个template参数(大多数情况下是template template参数)的class template,每一个参数代表一个policy。host class的所有技能都来自policies,运作起来就是一个聚合了数个policies的容器。示例如下:
#include <iostream>
#include <vector>
#include <map>
#include <type_traits>
#include <typeinfo>
#include <malloc.h>
#include <string>
using namespace std;
/* new 运算子*/
template <class T>
struct OpNewCreator
{
static T* Create()
{
return new T;
}
~OpNewCreator()
{
cout << "destory the existing OpNewCreator Proxy Object" << endl;
}
};
/* malloc()配合placement new运算子*/
template <class T>
struct MallocCreator
{
static T* Create()
{
void* buf = malloc(sizeof(T));
if (!buf) return 0;
return new(buf) T; //placement new运算子,在给定指针区域上初始化
}
};
/*clone方式,根据传递的对象指针直接调用clone()函数,无需直接调用类型T*/
template <class T>
struct PrototypeCreator
{
PrototypeCreator(T* pObj = 0) : pPrototype_(pObj) {}
T* Create(){
return pPrototype_ ? pPrototype_->Clone() : 0;
}
T* GetPrototype() { return pPrototype_; }
void SetPrototype(T* pObj) { pPrototype_ = pObj; }
protected:
~PrototypeCreator()
{
cout << "deleting the existing PrototypeCreator Object" <<endl;
}
private:
T* pPrototype_;
};
typedef std::vector<int> Widget;
typedef std::map<int,string> Gadget;
//template template参数的使用,可以保留host-class的动态性
template < template <class Created> class CreationPolicy = OpNewCreator >
class WidgetManager : public CreationPolicy<Widget>
{
public:
Gadget* DoSomething()
{
Gadget* pW = CreationPolicy<Gadget>().Create(); //CreationPolicy可以被二次具象化
//因为这时WidgetManager继承的是CreationPolicy<Widget>
//故而无法在这个函数内部,主动调用CreationPolicy<Gadget>的析构函数
//必须主动调用
}
//C++标准规定:如果一个模板类中有一个成员函数并没有在main阶段被用到,
//它其实是不会被编译器实现,编译器不会理会它,甚至也不对它进行语法检验
void SwitchPrototype(Widget* pNewPrototype)
{
CreationPolicy<Widget>& myPolicy = *this;
delete myPolicy.GetPrototype(); //在子类中调用父类的析构函数,意味着父类中必须实现析构函数,否则父类中默认的析构函数很可能得不到我们期望的效果
myPolicy.SetPrototype( pNewPrototype );
}
/*
上面这个函数,显然只有在CreationPolicy为PrototypeCreator时才有意义,但是如果用户采用
其他两个policy,那么显然这种函数中的很多语义是不存在的,编译器是否会报错呢?
1. 如果采用PrototypeCreator Policy来具象WidgetManager,那么显然SwitchPrototype是有意义的;
2. 如果采用了其他两种policy,但是从未在main阶段试图使用SwitchPrototype,那么编译器是不会报错
但是如果试图使用SwitchPrototype,那么编译器则会报错
*/
};
typedef WidgetManager<OpNewCreator> MyWidgetMgr;
typedef WidgetManager<PrototypeCreator> SecondMgr;
/*
Police-based class机制是由template和多重继承组成,一个class如果使用了policies,我们称其为
host class,是一个拥有多个template参数(大多数情况下是template template参数)的class template
,每一个参数代表一个policy。host class的所有技能都来自policies,运作起来就是一个聚合了数个
policies的容器。
*/
int main()
{
MyWidgetMgr wm;
OpNewCreator<Widget>* pCreator = &wm; //采用父类类型指针强制指向子类对象,是合理的
cout << typeid(*pCreator->Create()).name() << endl; //St6vectorIiSaIiEE
cout << typeid(pCreator->Create()).name() << endl; //PSt6vectorIiSaIiEE
cout << typeid(wm.DoSomething()).name() << endl; //PSt3mapIiSsSt4lessIiESaISt4pairIKiSsEEE
//delete pCreator; //这种直接main使用层次,手动析构底层对象的方式显然并不符合管理分权
//无疑增加了库使用者的使用权限以及负担,并且可能带来不必要的影响
/***************************************
Widget* pNewWidget = new Widget;
wm.SwitchPrototype(pNewWidget);
error:struct OpNewCreator<std::vector<int> > has no member named 'GetPrototype'
即对于template class,编译器采用的是懒惰编译的方式,如果在main阶段并没有使用到
template class中的某个member func,则该func并不会被编译器检查语义并实现,所以此
前的内容都没问题,只有当本区域的代码出现,编译器才会报错,这种借助C++特性以及
不完全具象化得到的动态选择性无疑使得库的使用者获得更广阔的自由度,能进能退,即使得
使用者在充分了解库之后自然地获取更多的额外操作,又能够姿态优雅地将库无缝地对接
纪律性高的最小化policy
**************************************/
SecondMgr sm;
sm.SwitchPrototype(new Widget); //成功编译
/***************************************
当把policies组合起来,便是它们最有用的时候,一般而言,一个高度可组装化的class会
运用数个policies来达成其运作上的丰富度。但是一般在实际情况下,超过4-6个的template
参数将会导致各policy间合作运行的笨拙,所以要适量。
template <
class T, //被指向的对象类别
template <class> class CheckingPolicy, //检查方案policy
template <class> class ThreadModel> //线程类型方案policy
class SmartPtr;
这种写法,便可以让SmartPtr变成为【整合数个policy方案】的中间层,而非一成不变的灌装
实作品。这种方式实现的SmartPtr,便是赋予使用者更多自由度
typedef SmartPtr<Widget, NoChecking, SingleThread> WidgetPtr;
typedef SmartPtr<Widget, EnforceNotNull, SingleThread> SafeWidgetPtr;
//Checking Policy
template <class T>
struct NoChecking
{
static void Check(T*) {}
};
template <class T>
struct EnforceNotNull
{
class NullPointerException : public std:exception { ... };
static void Check(T* ptr)
{
if (!ptr) throw NullPointerException();
}
};
template <class T>
struct EnsureNotNull
{
static void Check(T*& ptr)
{
if (!ptr) ptr = GetDefaultValue();
}
};
template <
class T,
template <class> class CheckingPolicy,
template <class> class ThreadingModel
>
class SmartPtr
: public CheckingPolicy<T>
, public ThreadModel<SmartPtr>
{
...
T* operator->()
{
typename threadingModel<SmartPtr>::Lock guard(*this);
CheckingPolicy<T>::Check(pointee_);
return pointee_;
}
private:
T* pointee_;
};
***************************************/
/***************************************
###用Policy Classes定制结构
template <class T>
class DefaultSmartPtrStorage
{
public:
typedef T* PointerType;
typedef T& ReferenceType;
protected:
PointerType GetPointer() { return ptr_; }
void SetPointer(PointerType ptr) { ptr_ = ptr; }
private:
PointerType ptr_;
};
这样就可以将上面template class中所有明文的T*指针替换成structure class对象
因为并非所有场景下的指针信息都是裸指针,比如Windows系统下并是采用Handle句柄
该Handle句柄此前我的文章中也有讲述,是系统内部的查表索引号,是一个整数,可以
称之为间接指针,但需要多绕几道,这时采用上面的structure policy便是一种可
整合更多使用场景的手段
***************************************/
/***************************************
Policy-based class,基于不同policy组合生成的两种对象间是否可以自由转换?
比如上面的SmartPtr存在一个无需事前检验指针的SmartPtr:FastWidgetPtr,还有
正常场景下使用的SafeWidgetPtr,C++支持隐式转换,理论上SafeWidgetPtr的限制
更多,符合从non-const转换为const的限制,即C++更倾向于支持限制级别低转换为
限制级别高的对象的隐式转换,并且也更符合使用规范
policies之间彼此转换的各种方法中,最好又具有扩充性的实现方式是以policy来控制
SmartPtr对象的拷贝和初始化,如下
template <
class T,
template <class> class CheckingPolicy
>
class SmartPtr : public CheckingPolicy<T>
{
...
template<
class T1,
template <class> class CP1
>
SmartPtr (const SmartPtr<T1, CP1>& other)
: ptr_(other.ptr_), CheckingPolicy<T>(other)
{ ... }
};
假设存在一个ExtendedWidget继承自Widget,那么以一个Smart<ExtendedWidget, NoChecking>
对象初始化一个SmartPtr<Widget, NoChecking>对象,这种转化编译器是可以通过的,因为
将ExtendWidget*转换为Widget*指针是属于类指针退化范围,使用SmartPtr<Widget, NoChecking>
初始化NoChecking也是可行的,因为SmartPtr<Widget, NoChecking>本身就继承自其Policy,
但是如果以SmartPtr<ExtendedWidget, NoChecking>初始化一个SmartPtr<Widget, EnforceNotNull>
问题便出现在用SmartPtr<ExtendedWidget, NoChecking>匹配EnforceNotNull,
如果EnforceNotNull类中的初始化函数可以接受NoChecking对象,或者NoChecking内部定义了
自己转换为EnforceNotNull的转型运算子,那么转换便可以进行,否则,编译器报错
***************************************/
return 0;
}
其实从上述讲解policy-based class的demo中便可以看到,通过policy的视角分解问题,并且充分利用多重继承以及template可以完成很漂亮弹性很强的精美库。虽然严格来说,policy-based class和策略模式的使用场景并不完全重合,但无疑这种设计思想是比策略模式更为优雅的。
4.使用场景
如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
在处理多种可能的情况时策略模式可以避免使用嵌套的if-else或switch-case语句。通过将每个条件与一个策略类关联,可以动态地选择合适的策略。
如果需要根据不同的条件或参数选择不同的算法,策略模式可以帮助实现这一自标。客户端代码可以与一组策略类交互,并在运行时选择最合适的算法。
5.总结
优点:
灵活性和可扩展性:策略模式提供了一种灵活的机制来改变对象的行为。通过使用不同的策略类,可以在不修改原有代码的基础上,方便地增加新的策略。这有助于保持软件的可扩展性和灵活性。
消除条件语句:策略模式通过消除显式的条件语句(如if-else或switch-case语句),使得代码更加简洁易读。客户端只需要与抽象的策略接口交互,而无需关心具体的实现细节。
更好的组织代码:策略模式有助于将相关的算法和行为组织在一起,形成独立的策略类。这有助于提高代码的组织性和可维护性。
便于替换算法:策略模式使得算法的替换变得更加简单。客户端代码与策略接口的交互保持不变,可以随时替换为不同的实现。这对于测试和调试也很有帮助。
支持开放-封闭原则:策略模式符合开放封闭原则、即软件实体(类、模块、函数等)应该对扩展开放,而对修改封闭。通过将算法封装在独立的策略类中,可以方便地添加新的策略,而无需修改现有代码。
缺点:
增加了类的数量:使用策略模式会增加系统的类数量,这可能会使得系统变得更加复杂。
客户端需要知道所有策略:客户端代码必须知道所有的策略类,并根据真体情况选择合适的策略。这可能会限制客户端的灵活性和可扩展性。
违反最少知识原则:最少知识原则要求一个对象应该尽量少的了解其他对象的信息。但在策略模式中,客户端可能需要了解所有策略类的信息,这可能违反了最少知识原则。
可能会导致设计过度复杂:如果过度使用策略模式,可能会导致设计变得过于复杂和繁锁,增加了维护的难度。
参考:
<<C++设计新思维>>第一章