zoukankan      html  css  js  c++  java
  • 书店会员销售系统(二)

    书店会员销售系统(二)
                         ――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

  • 相关阅读:
    安装Windows 和 Linux双系统(vmware) Centos7
    Nginx
    rsync详细配置
    19、Squid代理服务器
    5、SAMBA服务二:配置实例
    5、SAMBA服务一:参数详解
    4、NFS
    1、网络基本配置
    Spring data mongodb使用
    win下MongoDB使用
  • 原文地址:https://www.cnblogs.com/goodcandle/p/booksell2.html
Copyright © 2011-2022 走看看