目录:
策略模式的核心是封装各等效算法的多样性,将算法的选择(常用的是switch结构或者高端点的用反射机制)隐藏在中间层,从而解放使用者的编程复杂度。demo也很简单,参考自大话设计模式中的收银台程序来进行改造。
#include
using namespace std;
class CashSuper
{
friend class CashContext;
protected:
virtual double acceptCash(double money) = 0;
};
class CashNormal : public CashSuper
{
public:
double acceptCash(double money)
{
return money;
}
};
class CashRebate : public CashSuper
{
double Rebate;
public:
CashRebate(double discount) : Rebate(discount) {}
double acceptCash(double money)
{
return money * Rebate;
}
};
class CashReturn : public CashSuper
{
double baseCash, returnCash;
public:
CashReturn(double baseC, double returnC ) : baseCash(baseC), returnCash(returnC) {}
double acceptCash(double money)
{
if (money >= baseCash) return money - returnCash;
return money;
}
};
class CashContext
{
CashSuper* cs;
char StrategyType;
public:
CashContext() {}
CashContext(char & SType);
void operator=(char & SType);
double GetResult(double money)
{
if (cs == NULL) { cout << "The Strategy is undefined. " << endl; return -1;}
return cs->acceptCash(money);
}
};
CashContext::CashContext(char & SType) : StrategyType(SType)
{
switch (StrategyType)
{
case('a'):
case('A'):
cs = new CashNormal;
break;
case('b'):
case('B'):
cs = new CashRebate(0.8);
break;
case('c'):
case('C'):
cs = new CashReturn(1000, 200);
break;
default:
cs = NULL;
break;
}
}
void CashContext::operator=(char & SType)
{
StrategyType = SType;
switch (StrategyType)
{
case('a'):
case('A'):
cs = new CashNormal;
break;
case('b'):
case('B'):
cs = new CashRebate(0.8);
break;
case('c'):
case('C'):
cs = new CashReturn(1000, 200);
break;
default:
cs = NULL;
break;
}
}
int main()
{
cout << "Please enter a b c to invoke different cash Strategy: " << endl;
char type;
int ShouldPay = 1280;
CashContext cashC;
while (cin >> type)
{
cashC = type;
cout << "The real pay is : " << cashC.GetResult(ShouldPay) << endl;
}
}
但是可以看到,其实臃肿地代码并没有消除,只不过是转移到了背后的库。并且整体的布局和代码形式依旧是僵化的,并不符合时下的泛型编程的美。所以便需要转移到本文的主角:policy-based class。
Police-based class
机制是由template和多重继承组成,一个class如果使用了policies,我们称其为host class
,是一个拥有多个template参数(大多数情况下是template template
参数)的class template
,每一个参数代表一个policy。host class
的所有技能都来自policies,运作起来就是一个聚合了数个policies的容器。
#include
#include
#include
#include
#include
#include
#include
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" <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
{
public:
Gadget* DoSomething()
{
Gadget* pW = CreationPolicy().Create(); //CreationPolicy可以被二次具象化
//因为这时WidgetManager继承的是CreationPolicy
//故而无法在这个函数内部,主动调用CreationPolicy的析构函数
//必须主动调用
}
//C++标准规定:如果一个模板类中有一个成员函数并没有在main阶段被用到,
//它其实是不会被编译器实现,编译器不会理会它,甚至也不对它进行语法检验
void SwitchPrototype(Widget* pNewPrototype)
{
CreationPolicy& myPolicy = *this;
delete myPolicy.GetPrototype(); //在子类中调用父类的析构函数,意味着父类中必须实现析构函数,否则父类中默认的析构函数很可能得不到我们期望的效果
myPolicy.SetPrototype( pNewPrototype );
}
/*
上面这个函数,显然只有在CreationPolicy为PrototypeCreator时才有意义,但是如果用户采用
其他两个policy,那么显然这种函数中的很多语义是不存在的,编译器是否会报错呢?
1. 如果采用PrototypeCreator Policy来具象WidgetManager,那么显然SwitchPrototype是有意义的;
2. 如果采用了其他两种policy,但是从未在main阶段试图使用SwitchPrototype,那么编译器是不会报错
但是如果试图使用SwitchPrototype,那么编译器则会报错
*/
};
typedef WidgetManager MyWidgetMgr;
typedef WidgetManager SecondMgr;
/*
Police-based class机制是由template和多重继承组成,一个class如果使用了policies,我们称其为
host class,是一个拥有多个template参数(大多数情况下是template template参数)的class template
,每一个参数代表一个policy。host class的所有技能都来自policies,运作起来就是一个聚合了数个
policies的容器。
*/
int main()
{
MyWidgetMgr wm;
OpNewCreator* 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 > 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 CheckingPolicy, //检查方案policy
template class ThreadModel> //线程类型方案policy
class SmartPtr;
这种写法,便可以让SmartPtr变成为【整合数个policy方案】的中间层,而非一成不变的灌装
实作品。这种方式实现的SmartPtr,便是赋予使用者更多自由度
typedef SmartPtr WidgetPtr;
typedef SmartPtr SafeWidgetPtr;
//Checking Policy
template
struct NoChecking
{
static void Check(T*) {}
};
template
struct EnforceNotNull
{
class NullPointerException : public std:exception { ... };
static void Check(T* ptr)
{
if (!ptr) throw NullPointerException();
}
};
template
struct EnsureNotNull
{
static void Check(T*& ptr)
{
if (!ptr) ptr = GetDefaultValue();
}
};
template <
class T,
template class CheckingPolicy,
template class ThreadingModel
>
class SmartPtr
: public CheckingPolicy
, public ThreadModel
{
...
T* operator->()
{
typename threadingModel::Lock guard(*this);
CheckingPolicy::Check(pointee_);
return pointee_;
}
private:
T* pointee_;
};
***************************************/
/***************************************
###用Policy Classes定制结构
template
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 CheckingPolicy
>
class SmartPtr : public CheckingPolicy
{
...
template<
class T1,
template class CP1
>
SmartPtr (const SmartPtr& other)
: ptr_(other.ptr_), CheckingPolicy(other)
{ ... }
};
假设存在一个ExtendedWidget继承自Widget,那么以一个Smart
对象初始化一个SmartPtr对象,这种转化编译器是可以通过的,因为
将ExtendWidget*转换为Widget*指针是属于类指针退化范围,使用SmartPtr
初始化NoChecking也是可行的,因为SmartPtr本身就继承自其Policy,
但是如果以SmartPtr初始化一个SmartPtr
问题便出现在用SmartPtr匹配EnforceNotNull,
如果EnforceNotNull类中的初始化函数可以接受NoChecking对象,或者NoChecking内部定义了
自己转换为EnforceNotNull的转型运算子,那么转换便可以进行,否则,编译器报错
***************************************/
return 0;
}
其实从上述讲解policy-based class的demo中便可以看到,通过policy的视角分解问题,并且充分利用多重继承以及template可以完成很漂亮弹性很强的精美库。虽然严格来说,policy-based class和策略模式的使用场景并不完全重合,但无疑这种设计思想是比策略模式更为优雅的。
最后摘录一句超棒的话:
设计其实就是一种选择,大多数时候我们的困难并不在于找不到解决方案,而是有太多解决方案,你必须知道哪一组方案可以圆满地解决问题。大至架构层面,小至代码片段,都需要抉择。此外,抉择是可以组合的,这给设计带来了可怕的多样性。