1.4 面向对象编程
所有编程初学者碰到问题都会直觉地用计算机能够理解的逻辑来描述和表达待解决的问题及具体的求解过程。这其实是用计算机的方式去思考,比如计算器这个程序:先要求输入两个数和运算符号,然后根据运算符号判断选择如何运算,得到结果,这本身没有错,但这样的思维却使得我们的程序只为满足实现当前的需求。
这样的程序不容易维护,不容易扩展,更不容易复用。从而达不到高质量代码的要求。
1.5 活字印刷,面向对象
曹操诗兴大发,写了一首诗:“喝酒唱歌,人生真爽……”,然后命印刷工匠刻版印刷以便流传天下。
样张出来以后,曹操一看觉得过于俗气,于是将“喝酒唱歌”改成了“对酒当歌”,于是就命工匠重新来过。工匠眼看连夜刻版之工彻底白费,心中叫苦不迭,但只得照办。
样张再次出来以后,曹操又觉得第二句写的不好,于是将“人生真爽”改成了“人生几何”,工匠得知后当即晕倒!
三国时期,由于活字印刷术还未发明,所以要改字的时候,就必须要整个刻版全部重新刻。如果有了活字印刷,则只需要改四个字即可,其余工作都未白做,岂不妙哉:
第一,要改,只需更改要改之字,此为可维护;
第二,这些字并非用完这次就无用,完全可以在后来的印刷中重复使用,此为可复用;
第三,此诗若要加字,只需另刻字加入即可,这是可扩展;
第四,字的排列其实可能是竖排可能是横排,此时只需将活字移动就可做到满足满足排列需求,这就是灵活性好。
而在活字印刷术出现之前,上面的四种特性都无法满足,要修改,必须重刻,要加字,必须重刻,要重新排列,必须重刻,印完这本书后,此版已无任何可再利用价值。
1.6 面向对象的好处
大鸟:“我以前也不懂,不过做了几年软件开发后,经历了太多类似曹操这样的客户要改变需求、更改最初想法的事件,才逐渐明白当中的道理”。
其实客观地说,客户的要求也并不过分,不就是改几个字吗,但面对已完成的程序代码,却是需要几乎重头来过的尴尬,这实在是痛苦不堪。
说白了,原因就是因为我们原先所写的程序,不容易维护,灵活性差,不容易扩展,更谈不上复用,因此面对需求变化,加班加点,对程序动大手术的那种无奈也就成了非常正常的事了。
之后当我学习了面向对象的分析设计编程思想,开始考虑通过封装、继承、多态吧程序的耦合度降低,传统印刷术的问题就在于所有的字都刻在同一版面上造成耦合度太高所致,开始用设计模式使得程序更加的灵活,容易修改,并且易于复用。这些都是面向对象带来的好处。
1.7 复制 VS 复用
有人说初级程序员的工作就是 Ctrl + C 和 Ctrl + V,这其实是非常不好的编码习惯,因为当你的代码中重复的代码多到一定程度,维护的时候,可能就是一场灾难。—— 越大的系统,这种方式带来的问题越严重,编程有一原则,就是用尽可能的办法去避免重复。
1.8 业务的封装
想想看,你写的这段代码,有哪些是和控制台无关的,而只是和计算器有关的?
小菜:“你的意思是分一个类出来?哦,对的,让计算和显示分开”。
“准确地说,就是让业务逻辑与界面逻辑分开,让它们之间的耦合度下降。只有分离开,才可以达到容易维护或扩展”。
初级的面向对象代码:
public class Operation { public static double GetResult(double numberA, double numberB, string operate) { double result = 0d; switch (operate) { case "+": result = numberA + numberB; breake; case "-": result = numberA - numberB; breake; case "*": result = numberA * numberB; breake; case "/": result = numberA / numberB; breake; } return result; } }
这种实现只是简单的利用了面向对象的封装特性,而没有使用面向对象的继承和多态两个特性。
1.9 进一步的实现(松耦合)
operation 运算类:
public class Operation { private double _numberA = 0; private double _numberB = 0; public double NumberA { get { return _numberA; } set { _numberA = value; } } public double NumberB { get { return _numberB; } set { _numberB = value; } } public virtual double GetResout() { double result = 0; return result; } }
加减乘除类:
/// <summary> /// 加法类,继承运算类 /// </summary> public class OperationAdd : Operation { public override double GetResout() { double result = 0; result = NumberA + NumberB; return result; } } /// <summary> /// 除法类,继承运算类 /// </summary> public class OperationDiv : Operation { public override double GetResout() { double result = 0; if (NumberB == 0) throw new Exception("除数不能为0。"); result = NumberA / NumberB; return result; } }
这里首先写了一个运算类,它有两个 Number 属性,主要用于计算器的前后数,然后有一个虚方法 GetResult(),用于得到结果。
然后我把加减乘除都写成了运算类的子类,继承它后重写了 GetResult() 方法。—— 这样(通过继承)如果要修改任何一个算法,就不需要提供其他算法的代码了。
1.10 简单工厂模式
现在就是如何实例化对象的问题了,这里采用“简单工厂模式”。
到底实例化谁,将来会不会增加实例化的对象,比如增加开根运算,这是很容易变化的地方,应该考虑用一个单独的类来做这个创造实例的过程,这就是工厂。
简单运算工厂类:
public class OperationFactory { public static Operation CreateOperate(string operate) { Operation oper = null; switch (operate) { case "+": oper = new OperationAdd(); break; case "/": oper = new OperationDiv(); break; } return oper; } }
这样,只需要输入运算符号,工厂就可以实例化出合适的对象。
客户端代码:
Operation oper; oper = OperationFactory.CreateOperate("+"); oper.NumberA = 1; oper.NumberB = 2; double result = oper.GetResout(); Console.WriteLine(result); Console.ReadLine();
编程是一门技术,更加是一门艺术,不能只满足于写完代码运行结果正确就完事,时常考虑如何让代码更加简练,更加容易维护,容易扩展和复用,只有这样才可以真正得到提高。