博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《大话设计模式》之--第1章 代码无错就是优?----简单工厂模式
阅读量:4207 次
发布时间:2019-05-26

本文共 10602 字,大约阅读时间需要 35 分钟。

http://blog.csdn.net/monkey_d_meng/article/details/5676112

第1章 代码无错就是优?----简单工厂模式

1.1面试受挫

       小菜今年计算机专业毕业,学了不少的软件开发方面的东东,也能编个小程,踌躇满志地,一心想要找个好单位。当投递了无数份简历之后,终于收到一个单位的面试通知,小菜欣喜若狂。

       单位出了一道题目:“请用任意一种面向对象语言实现一个计算器控制台程序,要求输入两个数和运算符号,得到结果”。

       小菜一看,这还不简单,三下五除二,10分钟就搞定,写完后感觉没错误就交卷了。单位说一周内等通知,但半个月过去了都还木什么消息,小菜纳闷着呢,便找到了从事软件开发工作七年的表哥大鸟,请教原因,大鸟先让小菜把当时的代码给出来,小菜打开电脑10分钟就搞定,代码如下:

 

[java] 
  1. public class Program  
  2. {  
  3.     public static void main(String[] args)  
  4.     {  
  5.         InputStreamReader stdin = null;  
  6.         BufferedReader bufferReader = null;  
  7.   
  8.         stdin = new InputStreamReader(System.in);  
  9.         bufferReader = new BufferedReader(stdin);  
  10.   
  11.         System.out.print("请输入数字A:");  
  12.         String A = bufferReader.readLine();  
  13.         System.out.print("请选择运算符(+、-、*、/):");  
  14.         String B = bufferReader.readLine();  
  15.         System.out.print("请输入数字B:");  
  16.         String C = bufferReader.readLine();  
  17.   
  18.         String D = "";  
  19.         if ("+".equals(B))  
  20.         {  
  21.             D = String.valueOf(Integer.parseInt(A) + Integer.parseInt(C));  
  22.         }  
  23.         if ("-".equals(B))  
  24.         {  
  25.             D = String.valueOf(Integer.parseInt(A) - Integer.parseInt(C));  
  26.         }  
  27.         if ("*".equals(B))  
  28.         {  
  29.             D = String.valueOf(Integer.parseInt(A) * Integer.parseInt(C));  
  30.         }  
  31.         if ("/".equals(B))  
  32.         {  
  33.             D = String.valueOf(Integer.parseInt(A) / Integer.parseInt(C));  
  34.         }  
  35.   
  36.         System.out.println("结果是:" + D);  
  37.     }  
  38. }  

 

大鸟看后,哈哈大笑,说道:“小菜啊小菜,你上当鸟,人家单位出题的意思,你完全木有明白那,当然不会再联系你哇!”。

小菜不服气地说:“我的代码有错吗?单位题目不就是让我实现一个计算器吗?我写的哪有问题?”

 

1.2初学者代码毛病

大鸟说:“且先不说出题人的意思,单就你现在的代码,就有很多不足的地方,看你那东东,一看你就是个菜鸟~”。

首先,变量命名很随意,不规范;其次,每个判断分支都需要做,相当于做了三次无用功;最后,如果除数为0了或者输入不是数字,怎么办?

1.3代码规范

小菜:“嘿,你说的挺有道理的嘛,我刚才没在意,马上改给你看。”

 

 

[java] 
  1. public class Program2  
  2. {  
  3.     public static void main(String[] args)  
  4.     {  
  5.         InputStreamReader stdin = null;  
  6.         BufferedReader bufferReader = null;  
  7.   
  8.         stdin = new InputStreamReader(System.in);  
  9.         bufferReader = new BufferedReader(stdin);  
  10.   
  11.         try  
  12.         {  
  13.             System.out.print("请输入数字A:");  
  14.             String numberA = bufferReader.readLine();  
  15.             System.out.print("请选择运算符(+、-、*、/):");  
  16.             String operator = bufferReader.readLine();  
  17.             System.out.print("请输入数字B:");  
  18.             String numberB = bufferReader.readLine();  
  19.   
  20.             String result = "";  
  21.   
  22.             if ("+".equals(operator))  
  23.             {  
  24.                 result = String.valueOf(Integer.parseInt(numberA)  
  25.                         + Integer.parseInt(numberB));  
  26.             }  
  27.             else if ("-".equals(operator))  
  28.             {  
  29.                 result = String.valueOf(Integer.parseInt(numberA)  
  30.                         - Integer.parseInt(numberB));  
  31.             }  
  32.             else if ("*".equals(operator))  
  33.             {  
  34.                 result = String.valueOf(Integer.parseInt(numberA)  
  35.                         * Integer.parseInt(numberB));  
  36.             }  
  37.             else if ("/".equals(operator))  
  38.             {  
  39.                 if (!"0".equals(numberB))  
  40.                     result = String.valueOf(Integer.parseInt(numberA)  
  41.                             / Integer.parseInt(numberB));  
  42.                 else  
  43.                     result = "除数不能为0";  
  44.             }  
  45.   
  46.             System.out.println("结果是:" + result);  
  47.         }  
  48.         catch (IOException ex)  
  49.         {  
  50.             ex.printStackTrace();  
  51.         }  
  52.         catch (NumberFormatException ex)  
  53.         {  
  54.             System.out.println("您输入有误:" + ex.getMessage());  
  55.             ex.printStackTrace();  
  56.         }  
  57.     }  
  58. }  
[java] 
  1. public class Operation  
  2. {  
  3.     public static double getResult(double numberA, double numberB,  
  4.             String operator)  
  5.     {  
  6.         double result = 0;  
  7.           
  8.         if ("+".equals(operator))  
  9.             result = numberA + numberB;  
  10.         else if ("-".equals(operator))  
  11.             result = numberA - numberB;  
  12.         else if ("*".equals(operator))  
  13.             result = numberA * numberB;  
  14.         else if ("/".equals(operator))  
  15.             result = numberA / numberB;  
  16.   
  17.         return result;  
  18.     }  
  19. }  
  20. public class Main  
  21. {  
  22.     public static void main(String[] args)  
  23.     {  
  24.         InputStreamReader stdin = null;  
  25.         BufferedReader bufferReader = null;  
  26.   
  27.         stdin = new InputStreamReader(System.in);  
  28.         bufferReader = new BufferedReader(stdin);  
  29.         try  
  30.         {  
  31.             System.out.print("请输入数字A:");  
  32.             String numberA = bufferReader.readLine();  
  33.             System.out.print("请选择运算符(+、-、*、/):");  
  34.             String operator = bufferReader.readLine();  
  35.             System.out.print("请输入数字B:");  
  36.             String numberB = bufferReader.readLine();  
  37.   
  38.             String result = String.valueOf(Operation.getResult(Double  
  39.                     .parseDouble(numberA), Double.parseDouble(numberB),  
  40.                     operator));  
  41.   
  42.             System.out.println("结果是:" + result);  
  43.         }  
  44.         catch (Exception ex)  
  45.         {  
  46.             System.out.println("您输入有误:" + ex.getMessage());  
  47.             ex.printStackTrace();  
  48.         }  
  49.     }  
  50. }  

大鸟:“8错8错,改的挺快的嘛,至少就目前而言,代码实现要计算器是没有问题了,但问题是这样的代码是否合乎出题人的意思呢?”

小菜:“难道你的意思是说用面向对象?”

大鸟:“小菜终于领悟了!”。

1.4面向对象编程

小菜:“我总算明白了,他说用任意一种面向对象语言实现,那意思在于用面向对象的编程方法去实现,但当时我确实没有想到,还用的是面向过程的思维。”

大鸟:“所有编程初学者都会遇到这样的问题,就是碰到问题直觉地用计算机能够理解的逻辑来描述和表达,这实际上是用计算机的方式去思考,比如这个计算器的程序,先要求输入两个数和运算符号,然后根据运算符号判断选择如何运算,得到结果,这本身是完全正确的,但这种过程思维方式使得我们的程序只能满足实现当前的需求,程序不易维护、不易扩展、更不容易利用,从而只是完成了任务而已,根本达不到一个高质量代码的要求。”

1.5活字印刷,面向对象

大鸟:“给你讲个故事,你就理解了。”

大鸟:“话说三国时期,曹操带领百万大军攻打东吴,大军在长江赤壁驻扎,军舰连成一片,眼看就要把东吴给灭掉了,统一天下,曹操大悦,于是大宴宾客,在酒席间,曹操诗性大发,不觉呤道:‘喝酒唱歌,人生真爽…’。众人一听:‘丞相好诗哇!’于是忙命一印刷工匠刻版印刷,以便流传天下。”

“但是呢,当样版拿出来给曹操一看,曹操觉得不是很happy,说道:‘喝酒唱歌说的太过俗了,还是改成‘对酒当歌’比较happy!’,于是就命工匠重新刻版,工匠只能连夜刻版,之前的工作统统白费。”

“再拿样版给曹操看,曹操还是觉得不好,说:‘人生真爽太直接,还是改为‘人生几何’比较有意境些’…工匠听到后直接崩溃…!”

大鸟问:“小菜,你说说,这里面问题出在哪里?”

小菜:“三国时期活字印刷尚未发明,要改字的话,就需要将整个板面全部重新刻制”

大鸟:“说的好!如果有了活字印刷,只需要灵活地改动四个字就可以了,其余的工作都未白做。活字印刷就体现了面向对象的很多好处。”

“第一,要改,只需要改变需要改变的字,此为可维护;第二,这些字并非用完就无用了,完全可以在后来的印刷中重复使用,此为可复用;第三,此诗若加字,只需要另刻字加入即可,这是可扩展;第四字的排列其实可能是竖排,可能是横排,此时只需要将字移动到满足排列需求,此为灵活性好。”

“而在活字印刷术出现之前,上面的四种特性都无法得到满足,要个性,必须重刻;要加字,必须重刻;要重新排列,必须重刻;印完这本书后,此版本就无任何再复用价值。”

小菜:“还真是这样的呢,原来我一直很奇怪,为什么火药、指南针、造纸术都是从无到有,从未知到发现的伟大发明,而活字印刷术则仅仅是从刻版印刷到活字印刷的一次技术性的进步,为何不是评印刷术为四大发明之一呢?原来活字印刷成功是这个原因。”

1.6面向对象的好处

大鸟:“嗯,这下你明白了吧,我以前也不懂,后来做了软件开发几年后,经历了太多的类似曹操这样的客户要改变需求,客观来讲的话,客户的需求并不过人,不就改几个字吗,但面对已经完成的程序代码,却是需要几乎重头来过,这实在是龊。其原因还是在于,我们之前所写的代码,不容易维护、灵活性差、不容易扩展、更谈不上可复用,因此面对需求的变化,只能对程序大动手术。之后,我学习了面向对象的分析设计编程思想,开始考虑通过封装、继承、多态把程序的耦合度降低,传统的印刷术的问题就在于所有的字都刻在同一个版面上,造成了耦合度太高所致,开始用设计模式使得程序更加灵活,容易修改,并且易于复用。体会到了面向对象带来的好处,那种感觉真是happy的很哇!”

小菜:“我现在终于领悟了面试出题目的真正目的在于写出的代码易维护、可扩展、且易于复用,那该怎样做呢?”

1.7复制VS复用

大鸟:“现在,我们需要你再写一个windows的计算器,你现在的代码能不能复用?”

小菜:“那还不简单,把代码直接粘过去不就行了?改动又不是很大,不算麻烦。”

大鸟:“小菜看来就是个小菜,Ctrl+C和Ctrl+V是非常不好的编程习惯,因为当你的代码中重复到一定程序的时候,维护起来简直是一场灾难。越大的系统,这种方式带来的问题就越严重,编程有一个原则,就是尽可能避免重复。想想你写的哪些代码与控制台无关,而只和计算器有关?”

小菜:“你的意思是分一个类?对哇,就是将计算和显示分开。”

1.8业务的封装

大鸟:“准确地说,就是让业务逻辑与显示逻辑进行剥离,让它们之间的耦合度下降。只有分离,才可以达到容易维护或扩展。”

小菜:“那好,我来试试。”

 

[java] 
  1. public class Operation  
  2. {  
  3.     public static double getResult(double numberA, double numberB,  
  4.             String operator)  
  5.     {  
  6.         double result = 0;  
  7.           
  8.         if ("+".equals(operator))  
  9.             result = numberA + numberB;  
  10.         else if ("-".equals(operator))  
  11.             result = numberA - numberB;  
  12.         else if ("*".equals(operator))  
  13.             result = numberA * numberB;  
  14.         else if ("/".equals(operator))  
  15.             result = numberA / numberB;  
  16.   
  17.         return result;  
  18.     }  
  19. }  
  20. public class Main  
  21. {  
  22.     public static void main(String[] args)  
  23.     {  
  24.         InputStreamReader stdin = null;  
  25.         BufferedReader bufferReader = null;  
  26.   
  27.         stdin = new InputStreamReader(System.in);  
  28.         bufferReader = new BufferedReader(stdin);  
  29.         try  
  30.         {  
  31.             System.out.print("请输入数字A:");  
  32.             String numberA = bufferReader.readLine();  
  33.             System.out.print("请选择运算符(+、-、*、/):");  
  34.             String operator = bufferReader.readLine();  
  35.             System.out.print("请输入数字B:");  
  36.             String numberB = bufferReader.readLine();  
  37.   
  38.             String result = String.valueOf(Operation.getResult(Double  
  39.                     .parseDouble(numberA), Double.parseDouble(numberB),  
  40.                     operator));  
  41.   
  42.             System.out.println("结果是:" + result);  
  43.         }  
  44.         catch (Exception ex)  
  45.         {  
  46.             System.out.println("您输入有误:" + ex.getMessage());  
  47.             ex.printStackTrace();  
  48.         }  
  49.     }  
  50. }  

 

小菜:“鸟哥,我写好了,你来看看!”

大鸟:“孺鸟可教也,写的不错,这样就完全把业务和界面分离了。”

小菜心里暗骂:“你丫才是鸟呢。”口中说道:“如果你现在要我写一个Windows应用程序的计算器,我就可以复用这个运算类(Operation)了。”

大鸟:“不单是Windows程序,Web版程序需要运算可以用它,PDA、手机等需要移动系统的软件需要运算也可以用它。”

小菜:“哈,面向对象也不过如此,下回写类似代码就不怕了。”

大鸟:“别急,仅此而已,实在谈不上完全面向对象,你只用了面向对象三大特性中的一个,还有两个没有用呢?”

小菜:“面向对象三大特性不就是封装、继承和多态吗,这里我用到的应该是封装。这还不够吗?我实在看不出,这么小的程序如何用到继承。至于多态,其实我一直也不太了解它到底有什么好处,如何使用它。”

大鸟:“慢慢来嘛,要学的东东实在是太多了,你好好想想该如何应用面向对象的继承和多态。”

1.9紧耦合VS松耦合

第二天,小菜问道:“你说计算器这样的小程序还需要用到面向对象三大特性?继承和多态怎么可能用得上,我实在是不能理解。”

大鸟:“小菜很有钻石精神嘛,好,今天哥让你功力加深一级。你先要考虑一下,你昨天写的这个代码,能否做到很灵活的可修改和扩展呢?”

小菜:“我已经把业务和显示分离的啦,这不是很灵活了吗?”

大鸟:“那我问你,现在如果我希望增加一个开根运算,你如何改?”

小菜:“那只需要改Operation类就行了,加个if判断分支即可。”

大鸟:“问题是你要增加一个开根运算,却需要让加减乘除运算都得来参与编译,如果你一不小心把加法运算改成了减法,这岂不是大大的糟糕。打个比方,如果现在公司要求你为公司的薪资管理系统做维护,原来只有技术人员(月薪)、市场销售人员(底薪+提成)、经理(年薪+股份)三种运算方式,现要增加兼职工作人员(时薪)的算法,但按照你昨天的程序写法,公司就必须把包含原有三种算法的运算类给你,让你修改,你心中小算盘一打,‘TMD,公司给我的工资这么低,我真是郁闷啊,这下有机会了’,于是你除了增加了兼职算法外,在技术人员(月薪)算法中写了一句if(员工是小菜) {salary = salary * 1.1;},这就意味着,你的月薪每月都会增加10%,本来就是让你加一个功能,却使得原有的运行良好的功能代码产生了变化,这个风险太大了。你明白不?”

小菜:“原来如此,你的意思是我应该把加减乘除运算分离,修改其中任何一个不足以影响另外几个,增加运算算法也不影响其他代码。”

大鸟:“嗯,你再想想如何用继承和多态,你应该有感觉了。”

小菜:“耶,我马上去写。”

 

Operation运算基类

[java] 
  1. public class Operation  
  2. {  
  3.     private double  numberA = 0;  
  4.     private double  numberB = 0;  
  5.   
  6.     public double getResult() throws Exception  
  7.     {  
  8.         double result = 0;  
  9.         return result;  
  10.     }  
  11.   
  12.     public double getNumberA()  
  13.     {  
  14.         return numberA;  
  15.     }  
  16.   
  17.     public void setNumberA(double numberA)  
  18.     {  
  19.         this.numberA = numberA;  
  20.     }  
  21.   
  22.     public double getNumberB()  
  23.     {  
  24.         return numberB;  
  25.     }  
  26.   
  27.     public void setNumberB(double numberB)  
  28.     {  
  29.         this.numberB = numberB;  
  30.     }  
  31. }  

 

加减乘除类

 

[java] 
  1. public class OperationAdd extends Operation  
  2. {  
  3.     public double getResult()  
  4.     {  
  5.         double result = 0;  
  6.         result = getNumberA() + getNumberB();  
  7.         return result;  
  8.     }  
  9. }  
  10.   
  11. public class OperationSub extends Operation  
  12. {  
  13.     public double getResult()  
  14.     {  
  15.         double result = 0;  
  16.         result = getNumberA() - getNumberB();  
  17.         return result;  
  18.     }  
  19. }  
  20.   
  21. public class OperationMul extends Operation  
  22. {  
  23.     public double getResult()  
  24.     {  
  25.         double result = 0;  
  26.         result = getNumberA() * getNumberB();  
  27.         return result;  
  28.     }  
  29. }  
  30.   
  31. public class OperationDiv extends Operation  
  32. {  
  33.     public double getResult() throws Exception  
  34.     {  
  35.         double result = 0;  
  36.         if (getNumberB() == 0)  
  37.         {  
  38.             throw new Exception("除数不能为0");  
  39.         }  
  40.         result = getNumberA() / getNumberB();  
  41.         return result;  
  42.     }  
  43. }  

小菜:“大鸟哥,我按照你说的方法写了一部分,首先是一个运算基类,它有两个number属性,然后有一个虚方法getResult(),用于得到结果,然后我把加减乘除都写成了运算基类的子类,继承它后,重写了getResult()方法,这样如果需要修改任何一个算法,就不需要提供其他算法的代码了。但问题就来了,我如何能够让计算器知道我是希望调用哪一个算法呢?”

1.10简单工厂模式

8错8错,大大超出了我的想象,你现在的问题在于如何实例话对象,哈,今天心情8错,教你一招‘简单工厂模式’,也就是说,到底要实例化谁,将来会不会增加实例化对象,比如增加开根运算,这是很容易变化的地方,应该考虑用一个单独的类来做这个创造实例的过程,这就是工厂,来,我们看看这个类如何写。”

 

简单运算工厂类

[java] 
  1. public class OperationFactory  
  2. {  
  3.     public static Operation createOperation(String operate)  
  4.     {  
  5.         Operation oper = null;  
  6.   
  7.         if ("+".equals(operate))  
  8.             oper = new OperationAdd();  
  9.         else if ("-".equals(operate))  
  10.             oper = new OperationSub();  
  11.         else if ("*".equals(operate))  
  12.             oper = new OperationMul();  
  13.         else if ("/".equals(operate))  
  14.             oper = new OperationDiv();  
  15.   
  16.         return oper;  
  17.     }  
  18. }  

客户端类

 

[java] 
  1. public class Main  
  2. {  
  3.     public static void main(String[] args)  
  4.     {  
  5.         InputStreamReader stdin = null;  
  6.         BufferedReader buffer = null;  
  7.   
  8.         stdin = new InputStreamReader(System.in);  
  9.         buffer = new BufferedReader(stdin);  
  10.   
  11.         try  
  12.         {  
  13.             System.out.print("请输入数字A:");  
  14.             double numberA = Double.parseDouble(buffer.readLine());  
  15.             System.out.print("请选择运算符(+、-、*、/):");  
  16.             String operator = buffer.readLine();  
  17.             System.out.print("请输入数字B:");  
  18.             double numberB = Double.parseDouble(buffer.readLine());  
  19.   
  20.             Operation oper = OperationFactory.createOperation(operator);  
  21.             oper.setNumberA(numberA);  
  22.             oper.setNumberB(numberB);  
  23.             System.out.println("结果是:" + oper.getResult());  
  24.         }  
  25.         catch (NumberFormatException e)  
  26.         {  
  27.             e.printStackTrace();  
  28.         }  
  29.         catch (IOException e)  
  30.         {  
  31.             e.printStackTrace();  
  32.         }  
  33.     }  
  34. }  

大鸟:“哈,这样的话,不管你是控制台程序,Windows程序,Web程序,PDA或手机程序,都可以用这段代码来实现计算器的功能,如果有一天我们需要更改加法运算,我们只需要更改哪里?”

小菜:“改OperationAdd即可。”

大鸟:“那么我们需要增加各种复杂运算,比如平方根,立方根,自然对数,正弦余弦等,如何做?”

小菜:“只要增加相应的运算子类就可以了呀。”

大鸟:“嗯?够了吗?”

小菜:“对了,还需要去修改运算基类工厂,增加if条件判断的分支。”

大鸟:“哈,那才对,那么如果要修改界面呢?”

小菜:“那就去改界面的代码,跟运算无关嘛。”

大鸟:“我们来看看这几个类的结构图。”

你可能感兴趣的文章
[转]在ASP.NET 2.0中操作数据::创建一个数据访问层
查看>>
Linux命令之chmod详解
查看>>
【java小程序实战】小程序注销功能实现
查看>>
Java中子类能否继承父类的私有属性和方法
查看>>
JVM内存模型详解
查看>>
(六) Git--标签管理
查看>>
建造者模式(Builder)-设计模式(三)
查看>>
Linux-网络运维基础
查看>>
Verilog编程网站学习——门电路、组合电路、时序电路
查看>>
android——学生信息显示和添加
查看>>
Android——ImageSwitcher轮流显示动画
查看>>
Android——利用手机端的文件存储和SQLite实现一个拍照图片管理系统
查看>>
图像调优1:清晰度相关参数MTF,SFR,MTF50,MTF50P 以及TVL的概念以及换算说明
查看>>
罗永浩欲直播带货,京东说可以帮忙联系
查看>>
B站,正在变成下一个“公众号”?
查看>>
小米启动安心服务月 手机家电产品可免费清洁保养
查看>>
刘作虎:一加新品将全系支持 5G
查看>>
滴滴顺风车上线新功能,特殊时期便捷出行
查看>>
不会延期!iPhone 12S预计如期在9月发售:升级三星LTPO屏幕
查看>>
腾讯物联网操作系统TencentOS tiny线上移植大赛,王者机器人、QQ公仔、定制开发板等礼品等你来拿 !
查看>>