• 设计模式之模板方法模式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修饰的虚函数。

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


           

  • 相关阅读:
    Netty心跳检测
    探索UWB模块的潜力:智能家居与物联网的连接者
    进阶JAVA篇- Collcetions 工具类与集合的并发修改异常问题
    Kubernetes学习笔记-保障集群内节点和网络安全(3)限制pod使用安全相关的特性20220828
    NR CSI(二) the workflow of CSI report
    含文档+PPT+源码等]精品基于PHP实现的商城电商网站[包运行成功]
    八股文(数据库篇)——第十三天
    23年下半年软考系统集成(中项)报名已开始!
    SpringBoot-过滤器Filter+JWT令牌实现登录验证
    图解LeetCode——1742. 盒子中小球的最大数量(难度:简单)
  • 原文地址:https://blog.csdn.net/sinat_41928334/article/details/125069001