源码工作室

目标:通俗的语言说出通俗的技术
  博客园  :: 首页  :: 新随笔  :: 联系 :: 管理

书店会员销售系统(一)

Posted on 2006-03-09 15:38  源码工作室  阅读(4027)  评论(12编辑  收藏  举报
书店会员销售系统(一)
                     ――OORefactoring 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;
}



       因为篇幅的原因,所有出错处理都不考虑。
       编译一下,很多错误,主要是CCalculateCMember三个类没有创建,那接下来的工作当然是创建它们。
 
 会员类:

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;
}



计算类:

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;
}


    然后在编译程序,在测试用例中,我们希望的结果是:
    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;
}


    测试通过,一个基本功能的程序就编写成功了。当然一组测试用例远远是不够的,我这里也就不多写了。人还是人,错误是很难避免的,通过测试用例,能排除一大部分的问题,也为以后的程序改进提供的保障。
    也许会有人说,这样的设计太烂了。可如果需求不改变的话,至此就已经足够了。这不是本节的用意。

参考资料:
Refactoring: Improving the Design of Existing Code》 ――Martin Fowler