• 设计模式之模板方法模式C++实现


    需求场景

               饮料店制作两种含咖啡因的饮料:茶和咖啡。茶和咖啡都包含以下4个步骤:

                1、把水煮沸

                 2、冲泡

                 3、把饮料倒进杯子

                 4、加调料

               这4个步骤的顺序是固定的,不希望被改变的。其中第1步和第3步对茶和咖啡来说都是同样的处理方式,可以放在基类中处理。第3步和第4步针对两种饮料处理方式不同,其中茶需要用沸水侵泡茶叶,加牛奶(对,就是制作奶茶,哈哈哈),咖啡需要用沸水冲泡咖啡,加糖,这种不同的处理方式需要在子类中实现。

            基于上面的分析,可以得出以下类图结构

           

     

     

    模式定义

            模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

            类图如下

            

           如上面需求场景中,在咖啡因饮料基类中,定义制作饮料的步骤(prepareRecipe方法中),子类茶和咖啡都不能改变这个步骤,但具体的冲泡方法brew和加调料方法addCondiments在子类中被重新定义。

    代码实现

             针对需求场景中的例子代码实现如下

            

    1. // CaffeineBeverage.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    2. //
    3. #include
    4. //Base Class CaffeineBeverage
    5. class CaffeineBeverage {
    6. public:
    7. virtual void prepareRecipe()final//用final修饰不希望子类覆盖该方法
    8. {
    9. boilWater();
    10. brew();
    11. pourInCup();
    12. addCondiments();
    13. }
    14. virtual void brew() = 0;
    15. virtual void addCondiments() = 0;
    16. void boilWater()
    17. {
    18. std::cout << "Boiling water" << std::endl;
    19. }
    20. void pourInCup()
    21. {
    22. std::cout << "Pouring into cup" << std::endl;
    23. }
    24. };
    25. //Concrete Class Tea
    26. class Tea :public CaffeineBeverage {
    27. public:
    28. virtual void brew()override
    29. {
    30. std::cout << "Steeping the tea" << std::endl;
    31. }
    32. virtual void addCondiments()override
    33. {
    34. std::cout << "Adding milk" << std::endl;
    35. }
    36. };
    37. //Concrete Class Coffee
    38. class Coffee :public CaffeineBeverage {
    39. public:
    40. virtual void brew()override
    41. {
    42. std::cout << "Dripping COffe through filter" << std::endl;
    43. }
    44. virtual void addCondiments()override
    45. {
    46. std::cout << "Adding sugar" << std::endl;
    47. }
    48. };
    49. int main()
    50. {
    51. Tea myTea;
    52. Coffee myCoffee;
    53. myTea.prepareRecipe();
    54. std::cout << std::endl;
    55. myCoffee.prepareRecipe();
    56. }

       运行测试效果如下

       

            

    钩子函数

            钩子是一种被声明在基类中的方法,但只有空的或者默认的实现。钩子的存在可以让子类有能力对算法的不同点进行挂钩,要不要挂钩,由子类自行决定。

            以上面需求场景的例子举例钩子函数,我们加一个条件判断来决定是否要加调料,修改后代码如下

           

    1. virtual void prepareRecipe()final//用final修饰不希望子类覆盖该方法
    2. {
    3. boilWater();
    4. brew();
    5. pourInCup();
    6. if(customerWantsCondiments())
    7. addCondiments();
    8. }

            其中 customerWantsCondiments()在基类中的默认实现返回true,子类可以覆盖这个方法,来具体决定是否需要加饮料,这个函数就是钩子函数。

    1. virtual bool customerWantsCondiments()
    2. {
    3. return true;
    4. }

             在子类茶Tea中具体实现这个钩子函数,决定是否要加调料,代码如下

             

    1. virtual bool Tea::customerWantsCondiments()override
    2. {
    3. char wants = ' ';
    4. while (wants != 'Y' && wants != 'y'
    5. && wants != 'N' && wants != 'n')
    6. {
    7. std::cout << "Would you like milk with your tea?(Y/N)" << std::endl;
    8. std::cin >> wants;
    9. }
    10. if (wants == 'y' || wants == 'Y')
    11. return true;
    12. else if (wants == 'n' || wants == 'N')
    13. return false;
    14. }

     

             测试代码,运行效果如下

     

            什么时候使用抽象方法,什么时候使用钩子? 

           当子类必须提供算法中的某个方法或步骤的实现时使用抽象方法(纯虚函数),如果算法的函数是可选的就用钩子(虚函数),如果这个步骤不想被改变使用final修饰的虚函数。

          模板方法模式遵循 “别调用我,我会调用你”的设计原则,这防止了依赖腐败。当高层组件依赖底层组件,而底层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖底层组件时,依赖腐败就发生了。


           

  • 相关阅读:
    Linux回收内存的时候遇到PageWriteback和PageDirty脏页怎么处理?
    [Java] Java 函数式编程
    2022年11月份中国最具影响力的50位钧瓷匠人排行榜
    Android kotlin自定义圆形菜单的功能实现
    css flex 布局换行
    Mybatis-Plus的使用
    Django — 请求和响应
    嵌入式开发:简化传感器的5个技巧
    Qt6 设计工具
    知识工程复习之十八类重点问题(8-12)
  • 原文地址:https://blog.csdn.net/sinat_41928334/article/details/125069001