书店会员销售系统(一)
――OO、Refactoring and Design Pattern
本节目的:
1. 有一场景引入一个问题,学习测试驱动的编程方法,实现基本功能。
书店中的一幕:
营业员:“请问你有会员卡吗?”
顾客: “有的。”
顾客掏出钱包,拿出会员卡递给营业员,营业员输入会员卡号后开始计书的金额,期间有一本书的条形码扫描不出来,营业员手动输入。
营业员:“一共是126元。”
顾客递给营业员130元。
营业员:“收你130元,找你3元。”
顾客: “你算错了吧?”
营业员:“噢,不好意思,再给你1元。”(肯定在想哪件漂亮的衣服)
顾客: “我想问一下,我会员卡上的积分是多少了?”
营业员: “你总共消费累计2700元,积分为250点。”
顾客: “那我现在享受几折优惠了?”
营业员: “你现在可以享受8.5折优惠。”
顾客: “噢,我知道了,谢谢你。”
顾客提着书离开收银台。
我想这一场景大家可能都亲身经历过,当然不一定在书店。这就涉及到小店会员销售系统,如果要你编写这么一个系统,你会如何做呢?
我想你第一个想到的可能是数据库。是的,会员销售系统会记录很多的信息,肯定是要用到数据库的,但后面的叙述我会尽可能地去避免,为了简单而已。
我们先来分析一下问题。在上面的场景中涉及到一些对象:顾客、营业员、会员卡、书。顾客有顾客的信息,营业员有营业员的信息,会员卡记录了顾客的ID号,书的价格,在这里我先忽略书的价格,而是计算一次消费的金额,饭要一口一口地吃,还有营业员我们也可以先不涉及。
――OO、Refactoring and Design Pattern
本节目的:
1. 有一场景引入一个问题,学习测试驱动的编程方法,实现基本功能。
书店中的一幕:
营业员:“请问你有会员卡吗?”
顾客: “有的。”
顾客掏出钱包,拿出会员卡递给营业员,营业员输入会员卡号后开始计书的金额,期间有一本书的条形码扫描不出来,营业员手动输入。
营业员:“一共是126元。”
顾客递给营业员130元。
营业员:“收你130元,找你3元。”
顾客: “你算错了吧?”
营业员:“噢,不好意思,再给你1元。”(肯定在想哪件漂亮的衣服)
顾客: “我想问一下,我会员卡上的积分是多少了?”
营业员: “你总共消费累计2700元,积分为250点。”
顾客: “那我现在享受几折优惠了?”
营业员: “你现在可以享受8.5折优惠。”
顾客: “噢,我知道了,谢谢你。”
顾客提着书离开收银台。
我想你第一个想到的可能是数据库。是的,会员销售系统会记录很多的信息,肯定是要用到数据库的,但后面的叙述我会尽可能地去避免,为了简单而已。
我们先来分析一下问题。在上面的场景中涉及到一些对象:顾客、营业员、会员卡、书。顾客有顾客的信息,营业员有营业员的信息,会员卡记录了顾客的ID号,书的价格,在这里我先忽略书的价格,而是计算一次消费的金额,饭要一口一口地吃,还有营业员我们也可以先不涉及。
我们要实现的功能是:根据不同的会员,对其消费的金额进行打折,并累计消费点数,第一版UML图如下:
XP的原则是先写测试用例,然后使测试用例通过调试,得出正确的结果,我在这里也就按部就班的这么做。
int main(int argc, char* argv[])
{
char szMemberID[MAX_PATH];
strcpy(szMemberID,"00000001");
float fConsumeSum = 120.0;
int nPoint = 0;
CCalculate *pCalc = new CCalculate();
CMember *pMember = new CMember(szMemberID,1000.0,75);
nPoint = pMember->GetPoint();
nPoint += pCalc->CalculatePoint(fConsumeSum);
fConsumeSum = pCalc->CalcMoney(fConsumeSum,pMember);
pMember->SetPoint(nPoint);
assert(nPoint == 87);
assert(fConsumeSum == 120.0);
printf("Point = %d\n",nPoint);
printf("ConsumeSum = %f\n",fConsumeSum);
delete pCalc;
delete pMember;
return 0;
}
{
char szMemberID[MAX_PATH];
strcpy(szMemberID,"00000001");
float fConsumeSum = 120.0;
int nPoint = 0;
CCalculate *pCalc = new CCalculate();
CMember *pMember = new CMember(szMemberID,1000.0,75);
nPoint = pMember->GetPoint();
nPoint += pCalc->CalculatePoint(fConsumeSum);
fConsumeSum = pCalc->CalcMoney(fConsumeSum,pMember);
pMember->SetPoint(nPoint);
assert(nPoint == 87);
assert(fConsumeSum == 120.0);
printf("Point = %d\n",nPoint);
printf("ConsumeSum = %f\n",fConsumeSum);
delete pCalc;
delete pMember;
return 0;
}
因为篇幅的原因,所有出错处理都不考虑。
编译一下,很多错误,主要是CCalculate、CMember三个类没有创建,那接下来的工作当然是创建它们。
class CMember
{
public:
CMember(char *pszID,float fSum,int nPoint);
~CMember();
int GetPoint();
void SetPoint(int nPoint);
float GetRebate();
private:
char m_szID[MAX_PATH];
float m_fSum;
int m_nPoint;
};
CMember::CMember(char *pszID,float fSum,int nPoint)
{
if(pszID)
strcpy(m_szID,pszID);
m_fSum = fSum;
m_nPoint = nPoint;
}
int CMember::GetPoint()
{
return m_nPoint;
}
void CMember::SetPoint(int nPoint)
{
m_nPoint = nPoint;
}
float CMember::GetRebate()
{
if(m_nPoint<=100)
return 10;
else if(m_nPoint>100 && m_nPoint <200)
return 9.5;
else if(m_nPoint>=200 && m_nPoint <400)
return 9;
else if(m_nPoint>=400 && m_nPoint <600)
return 8.5;
else
return 8.0;
}
{
public:
CMember(char *pszID,float fSum,int nPoint);
~CMember();
int GetPoint();
void SetPoint(int nPoint);
float GetRebate();
private:
char m_szID[MAX_PATH];
float m_fSum;
int m_nPoint;
};
CMember::CMember(char *pszID,float fSum,int nPoint)
{
if(pszID)
strcpy(m_szID,pszID);
m_fSum = fSum;
m_nPoint = nPoint;
}
int CMember::GetPoint()
{
return m_nPoint;
}
void CMember::SetPoint(int nPoint)
{
m_nPoint = nPoint;
}
float CMember::GetRebate()
{
if(m_nPoint<=100)
return 10;
else if(m_nPoint>100 && m_nPoint <200)
return 9.5;
else if(m_nPoint>=200 && m_nPoint <400)
return 9;
else if(m_nPoint>=400 && m_nPoint <600)
return 8.5;
else
return 8.0;
}
计算类:
class CCalculate
{
public:
CCalculate();
~CCalculate();
int CalculatePoint(float fSum);
float CalcMoney(float fSum,CMember *pMember);
};
int CCalculate::CalculatePoint(float fSum)
{
int nPoint = (int)(fSum/10);
return nPoint;
}
float CCalculate::CalcMoney(float fSum,CMember *pMember)
{
float fRebateSum = 0.0;
fRebateSum = fSum * pMember->GetRebate();
return fRebateSum;
}
{
public:
CCalculate();
~CCalculate();
int CalculatePoint(float fSum);
float CalcMoney(float fSum,CMember *pMember);
};
int CCalculate::CalculatePoint(float fSum)
{
int nPoint = (int)(fSum/10);
return nPoint;
}
float CCalculate::CalcMoney(float fSum,CMember *pMember)
{
float fRebateSum = 0.0;
fRebateSum = fSum * pMember->GetRebate();
return fRebateSum;
}
然后在编译程序,在测试用例中,我们希望的结果是:
Point = 87
ConsumeSum = 120.000000
可运行的结果却是出现assert异常,仔细一查,输出结果为:
Point = 87
ConsumeSum = 1200.000000
原来在折扣上没有除10,修改代码:
float CCalculate::CalcMoney(float fSum,CMember *pMember)
{
float fRebateSum = 0.0;
fRebateSum = fSum * pMember->GetRebate()/10;
return fRebateSum;
}
{
float fRebateSum = 0.0;
fRebateSum = fSum * pMember->GetRebate()/10;
return fRebateSum;
}
测试通过,一个基本功能的程序就编写成功了。当然一组测试用例远远是不够的,我这里也就不多写了。人还是人,错误是很难避免的,通过测试用例,能排除一大部分的问题,也为以后的程序改进提供的保障。
也许会有人说,这样的设计太烂了。可如果需求不改变的话,至此就已经足够了。这不是本节的用意。
参考资料:
《Refactoring: Improving the Design of Existing Code》 ――Martin Fowler