源码工作室

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

书店会员销售系统(二)

Posted on 2006-03-10 14:51  源码工作室  阅读(2809)  评论(6编辑  收藏  举报
书店会员销售系统(二)
                     ――OORefactoring 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