大话设计模式 第1章 代码无错就是优? ——简单工厂模式
所有代码C++实现
1.1问题描述:
请用C++、Java、C#或VB.NET任意一种面向对象语言实现一个计算器控制台程序,要求输入两个数和运算符号,得到结果;
//version 1.1 #include<iostream> #include<stdio.h> using namespace std; int main() { double A,C,D; char B; printf("请输入数字A:\n"); scanf("%lf",&A); printf("请输入运算符号(+,-,*,/):\n"); scanf("%s",&B); printf("请输入数字B\n"); scanf("%lf",&C); if(B=='+') D=A+C; if(B=='-') D=A-C; if(B=='*') D=A*C; if(B=='/') D=A/C; printf("结果是:%lf",D); return 0; }
1.2 初学者代码毛病
大鸟说:“且先不说出题人的意思,单就你现在的代码,就有很多不足的地方需要改进。”
1、这样的命名是不规范的
2、判断分支,每个都要判断,做了许多无用功
3、如果除数是0,怎么办?用户输入的不是数字怎么办?
1.3 代码规范
//version 1.2 #include<iostream> #include<stdio.h> using namespace std; int main() { double numberA,numberB,result; char strOperate; try{ printf("请输入数字A:\n"); scanf("%lf",&numberA); printf("请输入运算符号(+,-,*,/):\n"); scanf("%s",&strOperate); printf("请输入数字B\n"); scanf("%lf",&numberB); switch(strOperate) { case '+': result=numberA+numberB; break; case '-': result=numberA-numberB; break; case '*': result=numberA*numberB; break; case '/': if(numberB!=0) result=numberA/numberB; else result=-1; break; } if(result==-1) printf("结果是:除数不能为0\n"); else printf("结果是:%lf",result); } catch(char *str) { printf("您的输入有错:%s",str); } return 0; }
1.4 面向对象编程
大鸟:“所有编程初学者都会有这样的问题,就是碰到问题就直觉地用计算机能够理解的逻辑来描述和表达待解决的问题及具体的求解过程。这其实是用计算机的方式去思考,比如计算器这个程序,先要求输入两个数和运算符号,然后根据运算符号判断选择如何运算,得到结果,这本身没有错,但这样的思维却使得我们的程序只为满足实现当前的需求,程序不容易维护,不容易扩展,更不容易复用。从而达不到高质量代码的要求。”
小菜:“鸟哥呀,我有点糊涂了,如何才能容易维护,容易扩展,又容易复用呢,能不能具体点?”
1.5 活字印刷,面向对象
“第一,要改,只需更改要改之字,此为可维护;
第二,这些字并非用完这次就无用,完全可以在后来的印刷中重复使用,此乃可复用;
第三,此诗若要加字,只需另刻字加入即可,这是可扩展;
第四,字的排列其实可能是竖排可能是横排,此时只需将活字移动就可做到满足排列需求,此是灵活性好。”
1.6 面向对象的好处
之后当我学习了面向对象的分析设计编程思想,开始考虑通过封装、继承、多态把程序的耦合度降低,
传统印刷术的问题就在于所有的字都刻在同一版面上造成耦合度太高所致,开始用设计模式使得程序更加的灵活,
容易修改,并且易于复用。
“呀是呀,你说得没错,中国古代的四大发明,另三种应该都是科技的进步,伟大的创造或发现。而唯有活字印刷,实在是思想的成功,面向对象的胜利。”小菜也兴奋起来:
“你的意思是,面试公司出题的目的是要我写出容易维护,容易扩展,又容易复用的计算器程序?那该如何做呀?”
1.7 复制vs.复用
大鸟:“小菜看来还是小菜呀,有人说初级程序员的工作就是Ctrl+C和Ctrl+V,这其实是非常不好的编码习惯,
因为当你的代码中重复的代码多到一定程度,维护的时候,可能就是一场灾难。越大的系统,这种方式带来的问题越严重,编程有一原则,就是用尽可能的办法去避免重复。想想看,你写的这段代码,有哪些是和控制台无关的,而只是和计算器有关的?”
1.8 业务的封装
大鸟:“准确地说,就是让业务逻辑与界面逻辑分开,让它们之间的耦合度下降。只有分离开,才可以达到容易维护或扩展。”
小菜:“让我来试试看。”
//version 1.3 #include<iostream> #include<stdio.h> using namespace std; double GetResult(double numberA,double numberB,char operate) { double result=0; switch(operate) { case '+': result=numberA+numberB; break; case '-': result=numberA-numberB; break; case '*': result=numberA*numberB; break; case '/': if(numberB!=0) result=numberA/numberB; break; } return result; } int main() { double numberA,numberB,result; char strOperate; try{ printf("请输入数字A:\n"); scanf("%lf",&numberA); printf("请输入运算符号(+,-,*,/):\n"); scanf("%s",&strOperate); printf("请输入数字B\n"); scanf("%lf",&numberB); result=GetResult(numberA,numberB,strOperate); if(result==-1) printf("结果是:除数不能为0\n"); else printf("结果是:%lf",result); }catch(char *str) { printf("您的输入有错:%s",str); } return 0; }
小菜:“面向对象三大特性不就是封装、继承和多态吗,这里我用到的应该是封装。这还不够吗?我实在看不出,这么小的程序如何用到继承。至于多态,其实我一直也不太了解它到底有什么好处,如何使用它。”
大鸟:“慢慢来,要学的东西多着呢,你好好想想该如何应用面向对象的继承和多态。”
1.9 紧耦合vs.松耦合
小菜:“我已经把业务和界面分离了呀,这不是很灵活了吗?”
大鸟:“那我问你,现在如果我希望增加一个开根(sqrt)运算,你如何改?”
小菜:“那只需要改Operation类就行了,在switch中加一个分支就行了。”
大鸟:“问题是你要加一个平方根运算,却需要让加减乘除的运算都得来参与编译,如果你一不小心,把加法运算改成了减法,这岂不是大大的糟糕。
1.10 简单工厂模式
//version 1.4 #include<iostream> #include<stdio.h> using namespace std; class Operation { private: double numberA=0,numberB=0; public: // Operation(){}//构造函数 // Operation(double _numberA=0,double _numberB=0):numberA(_numberA),numberB(_numberB){}; double getNumberA(){return numberA;} double getNumberB(){return numberB;} void setNumberA(double _numberA) { this->numberA=_numberA; } void setNumberB(double _numberB) { this->numberB=_numberB; } virtual double getResult() { double result=0; return result; } }; //加法类 class OperateAdd:public Operation { double getResult() { double result=0; result=getNumberA()+getNumberB(); return result; } }; //减法类 class OperateSub:public Operation { double getResult() { double result=0; result=getNumberA()-getNumberB(); return result; } }; //乘法类 class OperateMul:public Operation { double getResult() { double result=0; result=getNumberA()*getNumberB(); return result; } }; //除法类 class OperateDiv:public Operation { double getResult() { double result=0; if(getNumberB()==0) throw ; result=getNumberA()/getNumberB(); return result; } }; //简单工厂类 class OperationFactory { public : Operation* createOperate(char operate) { Operation *oper=new Operation(); switch(operate) { case '+': oper=new OperateAdd(); break; case '-': oper=new OperateSub(); break; case '*': oper=new OperateMul(); break; case '/': oper=new OperateDiv(); break; } return oper; } }; //客户端 int main() { double numberA,numberB,result; char strOperate; Operation *oper; OperationFactory *operFac; try{ printf("请输入数字A:\n"); scanf("%lf",&numberA); printf("请输入运算符号(+,-,*,/):\n"); scanf("%s",&strOperate); printf("请输入数字B\n"); scanf("%lf",&numberB); oper =operFac->createOperate(strOperate); oper->setNumberA(numberA); oper->setNumberB(numberB); result=oper->getResult(); if(result==-1) printf("结果是:除数不能为0\n"); else printf("结果是:%lf",result); }catch(char *str) { printf("您的输入有错:%s",str); } return 0; }
1.11 UML类图
自己看书;
主要包括类、接口,以及继承、实现、关联、聚合等;
大鸟:“吼吼,记住哦,编程是一门技术,更加是一门艺术,不能只满足于写完代码运行结果正确就完事,时常考虑如何让代码更加简练,更加容易维护,容易扩展和复用,只有这样才可以真正得到提高。写出优雅的代码真的是一种很爽的事情。UML类图也不是一学就会的,需要有一个慢慢熟练的过程。所谓学无止境,其实这才是理解面向对象的开始呢。”