策略模式
策略模式定义多种处理同一种场景的不同算法,这些算法可在不影响用户的情况下互相替换。
场景
应用场景
某会员制商场有两种级别会员:银卡会员、黄金会员,分别享有9折、8折购物优惠。同时不同的会员用户在生日当天购物,会赠送不同的生日礼物。
分析
场景比较简单,需要解决的问题是区分不同类型的顾客享有不同的权益(折扣和生日礼物)。
按照平常的编码习惯,通常会在需要区分用户等级的业务上加上if判断,实现不同等级顾客应享有的不同权益。这种方案能快速的解决实际问题,但是随着业务需要,商场又要引进更高逼格的会员类型,譬如白金会员、钻石会员等。此时,需要在散落在各处的业务代码上加上if补丁。这种做法会引来如下问题:
- 业务代码散落各处,容易出现漏加if的情况,又难以验证。
- 时间推移,if会越来越多,导致分支变多,代码走向模糊,影响代码维护。
解决方案
引入策略模式,将用户等级抽象,分别定义出用户所有的行为,不同等级的用户各自实现该等级应享有的权益。
类图
CShop:商场类。实现不同等级顾客的折扣结算、生日礼物等。持有CConsumer指针,根据需要指向具体的顾客实例(CCommonUser、CSilverUser、CGoldUser)。
CConsume:顾客抽象类。定义顾客所有的行为接口。
CCommonUser、CSilverUser、CGoldUser:具体顾客类。不同等级顾客实现有差异部分的接口。
效果
执行效果
代码语言:c 复制$ ./exe
---------------------------------
All Cost : 1000.00.
User Type : Common User.
Discount : 1.00.
Actual Payment: 1000.00.
---------------------------------
All Cost : 1000.00.
User Type : Silver User.
Discount : 0.90.
Actual Payment: 900.00.
---------------------------------
All Cost : 1000.00.
User Type : Gold User.
Discount : 0.80.
Actual Payment: 800.00.
客户端实现
代码语言:c 复制int main(int argc, char *argv[])
{
CShop theShop;
float cost = 1000.0;
// 普通用户
MAIN_LOG("n---------------------------------n");
MAIN_LOG(" All Cost : %0.2f. n"
" User Type : %s. n"
" Discount : %0.2f. n"
" Actual Payment: %0.2f. n",
cost,
theShop.GetUserDesc().c_str(),
theShop.GetCurrentDiscountRate(),
theShop.GetRealPrice(cost));
// 切换白银会员
MAIN_LOG("n---------------------------------n");
theShop.SetConsumer(COSUMER_SILVER);
MAIN_LOG(" All Cost : %0.2f. n"
" User Type : %s. n"
" Discount : %0.2f. n"
" Actual Payment: %0.2f. n",
cost,
theShop.GetUserDesc().c_str(),
theShop.GetCurrentDiscountRate(),
theShop.GetRealPrice(cost));
// 切换黄金会员
MAIN_LOG("n---------------------------------n");
theShop.SetConsumer(COSUMER_GOLD);
MAIN_LOG(" All Cost : %0.2f. n"
" User Type : %s. n"
" Discount : %0.2f. n"
" Actual Payment: %0.2f. n",
cost,
theShop.GetUserDesc().c_str(),
theShop.GetCurrentDiscountRate(),
theShop.GetRealPrice(cost));
return 0;
}
总结
- 策略模式的实现原理比较简单,主要是改变持有指针的指向,即可实现不同方案的切换。更改为外部条件输入匹配对应的实例,便可以做到客户端代码无需改变自动切换不同方案的效果。
- 满足开闭原则。当需增加策略时,只需要派生一个新的策略即可,而无需修改现有代码。相对以前的做法,更加安全快捷。
- 在代码运行时,也可以动态切换策略。
- 策略模式与命令模式有点相像。两者的实现方式类似,应对的场景不同。策略模式针对的是同一个行为不同的算法实现;命令模式针对是一个命令对应一个解决的方法。
- 全部源码可在公众号后台输入标题获取。
源码
商店类接口
代码语言:c 复制class CShop
{
public:
CShop();
~CShop();
std::string GetUserDesc() { return mConsumer->mUserDesc; }
float GetRealPrice(float price);
int BirthdayPresent();
int SetConsumer(EConsumerType type);
void SetCurrentDiscountRate(float rate);
float GetCurrentDiscountRate();
private:
CConsumer* mConsumer;
};
更新顾客类型
代码语言:c 复制int CShop::SetConsumer(EConsumerType type)
{
switch (type)
{
case COSUMER_COMMON:
mConsumer = CCommonUser::GetInstance();
break;
case COSUMER_SILVER:
mConsumer = CSilverUser::GetInstance();
break;
case COSUMER_GOLD:
mConsumer = CGoldUser::GetInstance();
break;
default:
break;
}
if (NULL == mConsumer) {
return -1;
}
return 0;
}
顾客类抽象接口
代码语言:c 复制class CConsumer
{
public:
float mDiscountRate;
std::string mUserDesc;
CConsumer() : mDiscountRate(1.0)
{
}
virtual ~CConsumer()
{
}
void SetDiscountRate(float rate)
{
mDiscountRate = rate;
}
float GetDiscountRate()
{
return mDiscountRate;
}
float GetRealPrice(float price)
{
return mDiscountRate * price;
}
virtual int GetBirthdayPresent() = 0;
};
具体顾客类:黄金会员
代码语言:c 复制class CGoldUser : public CConsumer
{
public:
CGoldUser();
~CGoldUser();
static CGoldUser* GetInstance();
int GetBirthdayPresent();
};
客户端接口
代码语言:c 复制int main(int argc, char *argv[])
{
CShop theShop;
float cost = 1000.0;
// 普通用户
MAIN_LOG("n---------------------------------n");
MAIN_LOG(" All Cost : %0.2f. n"
" User Type : %s. n"
" Discount : %0.2f. n"
" Actual Payment: %0.2f. n",
cost,
theShop.GetUserDesc().c_str(),
theShop.GetCurrentDiscountRate(),
theShop.GetRealPrice(cost));
// 切换白银会员
MAIN_LOG("n---------------------------------n");
theShop.SetConsumer(COSUMER_SILVER);
MAIN_LOG(" All Cost : %0.2f. n"
" User Type : %s. n"
" Discount : %0.2f. n"
" Actual Payment: %0.2f. n",
cost,
theShop.GetUserDesc().c_str(),
theShop.GetCurrentDiscountRate(),
theShop.GetRealPrice(cost));
// 切换黄金会员
MAIN_LOG("n---------------------------------n");
theShop.SetConsumer(COSUMER_GOLD);
MAIN_LOG(" All Cost : %0.2f. n"
" User Type : %s. n"
" Discount : %0.2f. n"
" Actual Payment: %0.2f. n",
cost,
theShop.GetUserDesc().c_str(),
theShop.GetCurrentDiscountRate(),
theShop.GetRealPrice(cost));
return 0;
}