引入:设计一个商场收银软件,营业员根据顾客购买的商品单价及数量,向客户收费。
一、收银系统V1.0
CashRegisterSystem1_0.h
#pragma once #include#include #include using namespace std; class CashRegisterSystem { private: double m_total; // 商品总价 double m_totalPrice; // 当前商品总价 int m_number; // 商品数量 double m_price; // 商品单价 vector shopVector; string shoplist; // 单次购物结果 public: void showList(); void btnOk_Click(int number, double price); void btnClr_Rst(); };
CashRegisterSystem1_0.cpp
#include"CashRegisterSystem1.h"
void CashRegisterSystem::btnOk_Click(int number, double price)
{
m_number = number;
m_price = price;
m_totalPrice = m_number * m_price;
m_total += m_totalPrice;
shoplist = "商品数量:" + to_string(m_number) + " 商品单价:" + to_string(m_price)
+ " 商品总价:" + to_string(m_totalPrice);
shopVector.push_back(shoplist);
shopVector.push_back("当前商品总价:" + to_string(m_total));
}
void CashRegisterSystem::btnClr_Rst()
{
shopVector.clear();
}
void CashRegisterSystem::showList()
{
for (auto it = shopVector.begin(); it != shopVector.end(); it++)
{
cout << (*it) << endl;
}
}
客户端程序:
#include#include"CashRegisterSystem1.h" using namespace std; int main() { CashRegisterSystem* crs = new CashRegisterSystem; crs->btnOk_Click(10, 200); crs->btnOk_Click(6, 18); crs->btnOk_Click(1, 800); crs->showList(); system("pause"); crs->btnClr_Rst(); crs->showList(); crs = NULL; delete crs; return 0; }
运行结果:
问题:
如果现在商场要打折,怎么办?折扣可能有很多种类?
改进方案1,通过使用switch语句,针对不同折扣在总价上乘以不同的系数。带来的问题,代码重复率太高,每个switch下的语句除了打折率,剩下都是相同的。此外,如果又提出新的打折方案,还需要进行修改。
改进方案2,使用简单工厂类。通过工厂类针对不同打折方案产生不同子类。带来的问题,产生的子类过多。
重点:面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。
二、收银系统V2.0
即便打折形式五花八门,但是打折的方法(算法)是一样的。
CashSuper.h 超级收费方法,收费方法的抽象类
#pragma once #includeusing namespace std; // 现金收费抽象类 class CashSuper { public: virtual double acceptCash(double money) { return money; }; };
CashNormal.h 正常收费模式
#pragma once #include#include"CashSuper.h" using namespace std; // 正常收费模式 class CashNormal : public CashSuper { public: double acceptCash(double money); };
CashNormal.cpp
#include "CashNormal.h"
double CashNormal::acceptCash(double money)
{
return money;
}
CashRebate.h 打折收费方式
#pragma once #include#include"CashSuper.h" using namespace std; // 打折模式 class CashRebate : public CashSuper { public: CashRebate(double moneyRebate); double acceptCash(double money); private: double m_moneyRebate = 1; };
CashRebate.cpp
#include "CashRebate.h"
CashRebate::CashRebate(double moneyRebate)
{
m_moneyRebate = moneyRebate;
}
double CashRebate::acceptCash(double money)
{
return money * m_moneyRebate;
}
CashReturn.h 返利收费模式
#pragma once #include#include"CashSuper.h" using namespace std; // 购物返利模式 class CashReturn :public CashSuper { private: double m_moneyCondition = 0.0; double m_moneyRetrun = 0.0; public: // @param: moneyCondition 满减条件 // @param: moneyReturn 返利值 // 每满300-100,则moneyCondition为300; // moneyReturn 为 100. CashReturn(double moneyCondition, double moenyReturn); double acceptCash(double money); };
CashReturn.cpp
#include "CashReturn.h"
CashReturn::CashReturn(double moneyCondition, double moneyReturn)
{
m_moneyCondition = moneyCondition;
m_moneyRetrun = moneyReturn;
}
double CashReturn::acceptCash(double money)
{
double result = money;
if (money >= m_moneyCondition)
{
result = money - int(money / m_moneyCondition) * m_moneyRetrun;
}
return result;
}
CashFactory.h 收费模式工厂
#pragma once #include#include
【Mark】学习点:C++ switch中case不接受string类型,可以使用map来讲string类型变量与int值一对一绑定。
客户端:
#include#include"CashFactory.h" #include"CashSuper.h" #include"CashNormal.h" #include"CashRebate.h" #include"CashReturn.h" using namespace std; void test01() { double totalPrices = 0.0; CashFactory* cf = new CashFactory; CashSuper* csuper; // 不打折 csuper = cf->createCashAccept("正常收费"); totalPrices = csuper->acceptCash(700); cout << "totalPrice:" << totalPrices << endl; cf = NULL; csuper = NULL; delete cf; delete csuper; } void test02() { double totalPrices = 0.0; CashFactory* cf = new CashFactory; CashSuper* csuper; // 不打折 csuper = cf->createCashAccept("满300返100"); totalPrices = csuper->acceptCash(700); cout << "totalPrice:" << totalPrices << endl; cf = NULL; csuper = NULL; delete cf; delete csuper; } void test03() { double totalPrices = 0.0; CashFactory* cf = new CashFactory; CashSuper* csuper; // 不打折 csuper = cf->createCashAccept("打8折"); totalPrices = csuper->acceptCash(700); cout << "totalPrice:" << totalPrices << endl; cf = NULL; csuper = NULL; delete cf; delete csuper; } int main() { //test01(); //test02(); test03(); system("pause"); return 0; }
运行结果:
虽然通过简单工厂类,以及抽象的收费类可以产生五花八门的收费方式。
但是每次增删收费方法,都需要对简单工厂类的代码进行修改。这不是一种好的方式。
三、策略模式
策略模式(Strategy):它定义了算法家族,分别放封装起来,让它们之间可以相互替换,此模式让算法的变化,不会影响到使用算法的客户。
四、策略模式下的收银系统V3.0
CashSuper相当于一个抽象策略类;
CashNormal、CashRebate、CashReturn相当于三个具体的算法。
因此需要增加一个CashContext类。
CashContext.h
#pragma once #include#include"CashSuper.h" using namespace std; class CashContext { private: CashSuper* cs; public: // 通过构造方法传入具体的收费策略 CashContext(CashSuper* csuper); double GetResult(double money); };
CashContext.cpp
#include "CashContext.h"
CashContext::CashContext(CashSuper* csuper)
{
this->cs = csuper;
}
double CashContext::GetResult(double money)
{
return cs->acceptCash(money);
}
客户端:
#include#include
运行结果:
五、策略与简单工厂类结合的收银系统V4.0
将简单工厂模式(SimpleFactory)和策略模式(Strategy)在CashContext中结合。
CashContextS_F.h
#pragma once #include#include
CashContextS_F.cpp
#include "CashContextS_F.h"
CashContextS_F::CashContextS_F(string type)
{
map type2IntMaP =
{
{"正常收费",1},
{"满300返100",2},
{"打8折",3},
};
int typeValue = type2IntMaP[type];
switch (typeValue) // 注意 “始化操作由‘case’标签跳过” 的错误
{
case 1:
{
CashNormal* cs0 = new CashNormal();
cs = cs0;
break;
}
case 2:
{
CashReturn* cs1 = new CashReturn(300, 100);
cs = cs1;
break;
}
case 3:
{
CashRebate* cs2 = new CashRebate(0.8);
cs = cs2;
break;
}
default:
break;
}
}
double CashContextS_F::GetResult(double money)
{
return cs->acceptCash(money);
}
【Mark】 记录一个错误: error C2360: “XX ”的初始化操作由“case”标签跳过。
解决办法就是把初始化变量的case语段都用“{}”括起来。
客户端:
#include#include
运行结果:
如此,客户端的代码更加简洁。同时客户端不需要认识(包含那么多头文件)更多的类。
六、策略模式总结
策略模式是一种定义了一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类直接的耦合。
优点:
Strategy类层为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
策略模式简化了的单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
策略模式封装了变化——实践中,可以用来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。
参考资料:《大话设计模式》,作者:程杰



