――OO、Refactoring and Design Pattern
本节目的:
1. 学习使用策略模式。
2. 使用重构手法。
客户: “我觉得你们的打折算法有点问题。”
项目经理:“有什么问题?
客户: “在这种算法中,顾客要消费至少1000元才能享受折扣优惠,我想根据书店的规模,采取不同的折扣 算法,这就需要让我能设置折扣算法。”
项目经理:“你说得比较有道理,我们会修改程序,满足你的要求。”
客户: “那下个版本给我实现这个功能吧?”
项目经理:“没有问题。”
听到这段对话,我们可以知道下一步我们需添加的新功能是配置折扣算法,但在实现之前,让我们先戴上“重构”的帽子。
在CMember会员类中,根据累计点数计算折扣这个功能有很不稳定的因素,我们应该把它提炼到一个类里去。
class CRebateRule
{
public:
CRebateRule();
~CRebateRule();
float CalcRebate(int nPoint);
};
float CRebateRule::CalcRebate(int nPoint)
{
if(nPoint<=100)
return 10;
else if(nPoint>100 && nPoint <200)
return 9.5;
else if(nPoint>=200 && nPoint <400)
return 9;
else if(nPoint>=400 && nPoint <600)
return 8.5;
else
return 8.0;
}
然后要对CMember::GetRebate()进行修改。
float CMember::GetRebate()
{
CRebateRule RebateRule;
return RebateRule.CalcRebate(m_nPoint);
}
编译程序,通过测试。
客户不是要配置自己的打折算法吗?我们可以把打折算法写在配置文件中,然后读取到数据结构中,这样就比较灵活了。现在是脱下“重构”的帽子,戴上“添加新功能”的帽子了。
为CRebateRule类添加一函数。
bool CRebateRule::Init(char *pszConfigFilename)
{
//从配置文件中读取打折算法,在这里我就不多写了。
return true;
}
float CRebateRule::CalcRebate(int nPoint)
{
//为了方便,就假设这是从配置文件中读取的。
if(nPoint<=100)
return 10;
else if(nPoint>100 && nPoint <200)
return 9.5;
else if(nPoint>=200 && nPoint <400)
return 9;
else if(nPoint>=400 && nPoint <600)
return 8.5;
else
return 8.0;
}
修改CMember::GetRebate()函数:
float CMember::GetRebate()
{
CRebateRule RebateRule;
RebateRule.Init("Configfile.txt");
return RebateRule.CalcRebate(m_nPoint);
}
编译程序,通过测试。
然而过了一段时间,客户又开始抱怨了:“我想在一些节假日实现折上加折的功能,现在的程序还不能实现。”
到现在,策略模式就该上场了,然而在加新功能之前,我们还是得考虑一下,问自己一个问题:“现在我加上这个功能,需要改动原有类的一些代码吗?”回答是肯定的。那不妨我们再戴上“重构”的帽子。
仔细观察CCalculate类,其中的两个函数,一个是累计会员的点数,一个是根据会员享受的折扣来计算消费的金额,我想我们可以把他们移到CMember类中去会更好些。
先修改main函数:
int main(int argc, char* argv[])
{
char szMemberID[MAX_PATH];
strcpy(szMemberID,"00000001");
float fConsumeSum = 120.0;
int nPoint = 0;
CMember *pMember = new CMember(szMemberID,1000.0,75);
nPoint = pMember->GetPoint();
nPoint += pMember->CalculatePoint(fConsumeSum);
fConsumeSum = pMember->CalcMoney(fConsumeSum);
pMember->SetPoint(nPoint);
assert(nPoint == 87);
assert(fConsumeSum == 120.0);
printf("Point = %d\n",nPoint);
printf("ConsumeSum = %f\n",fConsumeSum);
delete pMember;
return 0;
}
而CMember中加了两函数:
int CMember::CalculatePoint(float fSum)
{
int nPoint = (int)(fSum/10);
return nPoint;
}
float CMember::CalcMoney(float fSum)
{
float fRebateSum = 0.0;
fRebateSum = fSum * GetRebate()/10;
return fRebateSum;
}
编译程序,通过测试。
让我们再来分析一下,CalculatePoint函数是否要向外公开了,我们完全可以在计算折扣金额的时候一起累计点数。
所以main函数我们又可以修改了。
int main(int argc, char* argv[])
{
char szMemberID[MAX_PATH];
strcpy(szMemberID,"00000001");
float fConsumeSum = 120.0;
CMember *pMember = new CMember(szMemberID,1000.0,75);
fConsumeSum = pMember->CalcMoney(fConsumeSum);
assert(pMember->GetPoint() == 87);
assert(fConsumeSum == 120.0);
printf("Point = %d\n",nPoint);
printf("ConsumeSum = %f\n",fConsumeSum);
delete pMember;
return 0;
}
修改CMember::CalculatePoint为私有函数。
修改CMember::CalcMoney函数。
float CMember::CalcMoney(float fSum)
{
float fRebateSum = 0.0;
fRebateSum = fSum * GetRebate()/10;
CalculatePoint(fSum);
return fRebateSum;
}
编译程序通过,可调试不通过。简单的查了下原因,原来是CMember::CalculatePoint还没有修改正确。
修改后:
int CMember::CalculatePoint(float fSum)
{
m_nPoint = m_nPoint + (int)(fSum/10);
return m_nPoint;
}
编译程序,通过测试。
CMember::CalculatePoint还需要返回参数吗?改成void的吧。别忘了编译测试,保证每次的一小步,我们也能走到“黄河”。
再思考一下,CalcMoney这个名字适合吗?RebateSumAndCumulatePoint可能会更好点。
以上的重构都不是为了添加新功能,只是为了程序更简洁、更具可读性。下面的重构才开始为了添加新功能而做的。UML图如下:
把现有的CRebateRule改名为CNormalRebateRule,然后修改程序让其通过编译测试。再创建一个新的抽象类CRebateRule。
class CRebateRule
{
public:
virtual ~CRebateRule();
virtual bool Init(char *pszConfigFilename) = 0;
virtual float CalcRebate(int nPoint) = 0;
};
根据设计,我们继续修改程序,使它通过编译测试。到现在为止,我们可以加新功能了。这样我们添加CSpecialRebateRule类,现有的类都不需要修改了,戴上“添加新功能”的帽子吧。
我们先规定折上在打9折。先写main函数:
int main(int argc, char* argv[])
{
char szMemberID[MAX_PATH];
strcpy(szMemberID,"00000001");
float fConsumeSum = 120.0;
CMember *pMember = new CMember(szMemberID,1000.0,75);
CRebateRule *pRebateRule = new CNormalRebateRule;
pRebateRule->Init("Configfile.txt");
fConsumeSum = pMember->RebateSumAndCumulatePoint(fConsumeSum,pRebateRule);
assert(pMember->GetPoint() == 87);
assert(fConsumeSum == 120.0);
printf("Point = %d\n",pMember->GetPoint());
printf("ConsumeSum = %f\n",fConsumeSum);
delete pRebateRule;
pRebateRule = new CSpecialRebateRule;
pRebateRule->Init("Configfile.txt");
fConsumeSum = pMember->RebateSumAndCumulatePoint(300.0,pRebateRule);
assert(pMember->GetPoint() == 117);
assert(fConsumeSum == 270.0);
printf("Point = %d\n",pMember->GetPoint());
printf("ConsumeSum = %f\n",fConsumeSum);
delete pMember;
delete pRebateRule;
return 0;
}
增加CSpecialRebateRule类及函数,这里类的代码我就不写出来了,可以下载代码。
float CSpecialRebateRule::CalcRebate(int nPoint)
{
//假设现在用户已经享受9.5折的优惠。
return (9.5*9.0/10);
}
编译程序,通过测试。
也许有人会问,这里为何不用Factory Method设计模式呢?在这里折扣算法类只有CMember类使用,而不是像Log类样到处使用的,毕竟使用Factory Method还会引入Factory类,会增加程序的复杂度,在不必要用的时候,我们还是避免使用,简单的可以使用Simple Factory来替代。
记住一点:不要一开始就添加新功能,而是通过重构的手法,不改变软件的原来功能,逐步地修改程序,使其适合新功能的加入。
好了,说的也比较罗嗦了,但这是一个编程的过程。
使用C++语言编写,在VC++ 6.0环境调试通过。
下载代码
参考资料:
《Refactoring: Improving the Design of Existing Code》 ――Martin Fowler
《Design Patterns - Elements of Reusable Object-Oriented Software》 ――GoF