大话设计模式解读01-简单工厂模式

三尺非寒  论坛元老 | 2024-6-25 16:06:01 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1002|帖子 1002|积分 3006

本系列的文章,来介绍编程中的设计模式,介绍的内容主要为《大话设计模式》的读书条记,并改用C++语言来实现(书中使用的是.NET中的C#),本篇来学习第一章,介绍的设计模式是——简单工厂模式
1 面向对象编程

设计模式依靠与面向对象编程密不可分,因此在开始学习设计模式之前,先简单介绍下面向对象编程。
先来看一个小故事:
   话说三国时期,曹操在赤壁带领百万大军,眼看就要灭掉东吴,统一天下,非常高性,于是大宴文武。
  在酒席间,不觉吟到:“喝酒唱歌,人生真爽,…”,众文武齐呼:“丞相好诗!”,
  于是一臣子速速命令印刷工匠举行刻版印刷,以便传播天下。
  

   印刷工匠刻好样张,拿出来给曹操一看,曹操感觉不妥,
  说道:“喝与唱,此话过俗,应该改为对酒当歌较好!”,
  于是臣子就命令工匠重新来过,工匠眼看连夜刻版之功,彻底白费,心中叫苦不迭,只得照办。
  

   印刷工匠再次刻好样张,拿出来给曹操过目,曹操细细一品,以为照旧不好,
  说:“人生真爽太过直接,应该改为问句才够意境,因此应改为对酒当歌,人生几何”,
  当臣子再次转告工匠之时,工匠晕倒…
  

那,问题出在那里呢?
大概是三国时期还没有活字印刷术吧,所以要改字的时间,就必须整个刻板全部重新雕刻。
如果有了活字印刷术,其实只需要更改四个字即可,其余工作都未白做。

我们遐想编程,从这个小故事中,来了解一下编程中的一些思想:


  • 可维护:要改字,只需更改需要变更的字即可
  • 可复用:这些字并不是只是这次有用,后续如果在别的印刷中需要用,可重复使用
  • 可扩展:如果诗中需要加字,只需别的单独刻字即可
  • 机动性:字的分列可以横排,也可以竖排

面向对象编程,通过封装、继承和多态,把程序的耦合度降低
传统印刷术的问题就在于把所有字都刻在同一个版面上的耦合度太高。
使用设计模式可以使程序更加机动,容易修改,并易于复用。

2 计算器实例

下面以一个计算器的代码实例,来了解封装的思想,以及简单工厂模式的使用。
   题目:设计一个计算器控制台程序,输入为两个数和运算符,输出结果
  功能比力简单,先来看第一个版本的实现。
2.1 版本一:面向过程

第一个版本接纳面向过程的思想,从吸收用户输入,到数据运算,以及末了的输出,都是按次序在一个代码块中实现的:
  1. int main()
  2. {
  3.     float numA = 0;
  4.     float numB = 0;
  5.     float result = 0;
  6.     char operate;
  7.     bool bSuccess = true;
  8.    
  9.     printf("please input a num A:\n");
  10.     scanf("%f", &numA);
  11.     printf("please input a operate(+ - * \\):\n");
  12.     std::cin >> operate;
  13.     printf("please input a num B:\n");
  14.     scanf("%f", &numB);
  15.    
  16.     switch(operate)
  17.     {
  18.         case '+':
  19.         {
  20.             result = numA + numB;
  21.             break;
  22.         }
  23.         case '-':
  24.         {
  25.             result = numA - numB;
  26.             break;
  27.         }
  28.         case '*':
  29.         {
  30.             result = numA * numB;
  31.             break;
  32.         }
  33.         case '/':
  34.         {
  35.             if (numB == 0)
  36.             {
  37.                 bSuccess = false;
  38.                 printf("divisor cannot be 0!\n");
  39.                 break;
  40.             }
  41.             result = numA / numB;
  42.             break;
  43.         }
  44.         default:
  45.         {
  46.             bSuccess = false;
  47.             break;
  48.         }
  49.     }
  50.    
  51.     if (bSuccess)
  52.     {
  53.         printf("%f %c %f = %f\n", numA, operate, numB, result);
  54.     }
  55.     else
  56.     {
  57.         printf("[%f %c %f] calc fail!\n", numA, operate, numB);
  58.     }
  59.    
  60.     return 0;
  61. }
复制代码
该程序的运行结果如下图所示:

上述代码实现本身没有什么问题,但是,如果现在要再实现一个带有UI界面的计算器,代码能不能复用呢?很显然不可,代码都是在一起的。
因此,为了便于代码复用,可以将计算部分的代码和显示部分的代码分开,降低它们之间的耦合度
2.2 版本二:对业务封装

版本二则是对计算部分的业务代码显示部分的控制台输入输出代码分开。
计算部分的业务代码,设计一个Operation运算类,通过其成员函数GetResult来实现加减乘除运算。
2.2.1 业务代码

  1. class Operation
  2. {
  3. public:
  4.     bool GetResult(float numA, float numB, char operate, float &result)
  5.     {
  6.         bool bSuccess = true;
  7.         
  8.         switch(operate)
  9.         {
  10.             case '+':
  11.             {
  12.                 result = numA + numB;
  13.                 break;
  14.             }
  15.             case '-':
  16.             {
  17.                 result = numA - numB;
  18.                 break;
  19.             }
  20.             case '*':
  21.             {
  22.                 result = numA * numB;
  23.                 break;
  24.             }
  25.             case '/':
  26.             {
  27.                 if (numB == 0)
  28.                 {
  29.                     bSuccess = false;
  30.                     printf("divisor cannot be 0!\n");
  31.                     break;
  32.                 }
  33.                 result = numA / numB;
  34.                 break;
  35.             }
  36.             default:
  37.             {
  38.                 bSuccess = false;
  39.                 break;
  40.             }
  41.         }
  42.         
  43.         return bSuccess;
  44.     }
  45. };
复制代码
2.2.2 控制台界面代码

显示部分的控制台输入输出代码,还在main函数中。
  1. int main()
  2. {
  3.     float numA = 0;
  4.     float numB = 0;
  5.     float result = 0;
  6.     char operate;
  7.    
  8.     printf("please input a num A:\n");
  9.     scanf("%f", &numA);
  10.     printf("please input a operate(+ - * \\):\n");
  11.     std::cin >> operate;
  12.     printf("please input a num B:\n");
  13.     scanf("%f", &numB);
  14.    
  15.     Operation Op1;
  16.     bool bSuccess = Op1.GetResult(numA, numB, operate, result);
  17.    
  18.     if (bSuccess)
  19.     {
  20.         printf("%f %c %f = %f\n", numA, operate, numB, result);
  21.     }
  22.     else
  23.     {
  24.         printf("[%f %c %f] calc fail!\n", numA, operate, numB);
  25.     }
  26.    
  27.     return 0;
  28. }
复制代码
版本二的运行结果演示如下:

上述的版本二的代码实现,就用到了面向对象三大特性中的封装
那,上述代码,是否可以做到机动扩展?
好比,如果盼望增加一个开根号的运算,如果改?
按照现有逻辑,需要修改Operation运算类,在switch中增加一个分支。但如许,会需要加减乘除的逻辑再次到场编译,别的,如果在修改开根号的代码时,不小心改动了加减乘除的逻辑,影响就大了。
因此,可以使用面向对象中继承和多态的思想,来实现各个运算类的分离。
2.3 版本三:简单工厂

版本三用到了封装、继承、多态,以及通过简单工厂来实例化出符合的对象。
2.3.1 Operation运算类(父类)

Operation运算类为一个抽象类,是加减乘除类的父类。
该类包罗numA和numB两个成员变量,以及一个虚函数GetResult用于计算运算结果,各个子类中对其举行具体的实现。
  1. // 操作类(父类)
  2. class Operation
  3. {
  4. public:
  5.     float numA = 0;
  6.     float numB = 0;
  7.    
  8. public:
  9.     virtual float GetResult()
  10.     {
  11.         return 0;
  12.     };
  13. };
复制代码
2.3.2 加减乘除类(子类)

加减乘除子类通过公有继承Operation类,可以访问其共有成员变量numA和numB,并对GetResult方法举行具体的实现:
  1. // 加法类(子类)
  2. class OperationAdd : public Operation
  3. {
  4. public:
  5.     float GetResult()
  6.     {
  7.         return numA + numB;
  8.     }
  9. };
  10. // 减法类(子类)
  11. class OperationSub : public Operation
  12. {
  13. public:
  14.     float GetResult()
  15.     {
  16.         return numA - numB;
  17.     }
  18. };
  19. // 乘法类(子类)
  20. class OperationMul : public Operation
  21. {
  22. public:
  23.     float GetResult()
  24.     {
  25.         return numA * numB;
  26.     }
  27. };
  28. // 除法类(子类)
  29. class OperationDiv : public Operation
  30. {
  31. public:
  32.     float GetResult()
  33.     {
  34.         if (numB == 0)
  35.         {
  36.             printf("divisor cannot be 0!\n");
  37.             return 0;
  38.         }
  39.         return numA / numB;
  40.     }
  41. };
复制代码
2.3.3 简单运算工厂类

为了能方便地实例化加减乘除类,思量使用一个单独的类来做这个创造实例的过程,这个就是工厂。
设计一个OperationFactory类来实现,如许,只要输入运算的符号,就能实例化出符合的对象。
  1. // 简单工厂模式
  2. class OperationFactory
  3. {
  4. public:
  5.     Operation *createOperation(char operation)
  6.     {
  7.         Operation *oper = nullptr;
  8.         switch(operation)
  9.         {
  10.             case '+':
  11.             {
  12.                 oper = (Operation *)(new OperationAdd());
  13.                 break;
  14.             }
  15.             case '-':
  16.             {
  17.                 oper = (Operation *)(new OperationSub());
  18.                 break;
  19.             }
  20.             case '*':
  21.             {
  22.                 oper = (Operation *)(new OperationMul());
  23.                 break;
  24.             }
  25.             case '/':
  26.             {
  27.                 oper = (Operation *)(new OperationDiv());
  28.                 break;
  29.             }
  30.             default:
  31.             {
  32.                 break;
  33.             }
  34.         }
  35.         
  36.         return oper;
  37.     }
  38. };
复制代码
使用版本三,如果后续需要修改加法运算,只需要修改OperationAdd类中的内容即可,不会影响到别的计算类。
2.3.4 控制台界面代码

显示部分的控制台输入输出代码,还在main函数中。
通过多态,返回父类的方式,实现对应运算的计算结果。
  1. {
  2.     float numA = 0;
  3.     float numB = 0;
  4.     float result = 0;
  5.     char operate;
  6.    
  7.     printf("please input a num A:\n");
  8.     scanf("%f", &numA);
  9.     printf("please input a operate(+ - * \\):\n");
  10.     std::cin >> operate;
  11.     printf("please input a num B:\n");
  12.     scanf("%f", &numB);
  13.    
  14.     OperationFactory opFac;
  15.     Operation *oper = nullptr;
  16.     oper = opFac.createOperation(operate);
  17.     if (oper != nullptr)
  18.     {
  19.         oper->numA = numA;
  20.         oper->numB = numB;
  21.         result = oper->GetResult();
  22.         printf("%f %c %f = %f\n", numA, operate, numB, result);
  23.         
  24.         delete oper;
  25.     }
  26.     else
  27.     {
  28.         printf("[%f %c %f] calc fail!\n", numA, operate, numB);
  29.     }
  30.    
  31.     return 0;
  32. }
复制代码
版本三的运行结果演示如下:

版本三中,各个类之间的关系如下图所示:


  • 运算类是一个抽象类(类名用斜体表示),具有两个float范例的公有的(共有用**+号**)成员变量numA和numB以及一个GetResult公有方法
  • 四个计算类继承(继承空心三角+实线表示)运算类,并实现对应的GetResult方法
  • 简单工厂类依靠于(依靠箭头+虚线表示)运算类,通过createOperation方法实现运算类的实例化

3 总结

本篇主要介绍设计模式中的简单工厂模式,起首通过一个活字印刷的小故事来了解程序设计中的可维护、可复用、可扩展、机动性的思想,并引入面向对象设计模式中的三大基本思想:封装、继承、多态,然后通过一个计算器的代码实现的例子,通过C++实现了三个版本的代码,由浅到深地明白面向对象的设计思想以及简单工厂模式的使用。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

三尺非寒

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表