浅谈架构——面向对象
写在开始:本篇算是浅谈架构系列的正式文章了,计划整个系列文章从面向对象编程开始到设计模式、应用级架构设计模式、到以SOA思想为基础的产品线架构设计为止。每一个部分的编写思路是先列举一些知识相关的理论或设计原则,以及自己对这些原则的理解。接着会依照原则提出一些通用的或自己总结的设计方法和注意事项,这些方法都是我在工作或学习过程中验证过,不能保证这些都是正确的甚至可以说有些东西都是错误的,但至少他们在某一个时间段或者在某个场景下帮助我解决了一些实际的问题。这里借助简书这个平台把手里的砖头抛出去,万一能引出来一大堆璞玉来的话就赚了。如果大家发现什么不对或者不赞同的地方,请一定要留言或者发到我的邮箱modify961@126.com,一块来讨论一下,也可以加我的微信:y376214298。在这里先谢谢各位大神了。
这篇文章的标题叫“面向对象”,本来是想起名叫“面向对象程序设计”。但是写着写着就发现程序设计太大了,不是短短几个段落就能够概括的,借用《代码大全2》中的一个标题来说"设计是个无章法的过程(即使它能得出清爽的成果)”,设计本来就不是一个一蹴而就的事情,整个过程应该是在不断的设计、评估、讨论、试错、测试代码以及修改测试代码中不断演化而来的。自己本身能力就不够,如果强行要用几百个字说明白的话,就有点为赋新词强说愁的意思了。但是我会在接下来的系列文章里会用一些实例或者曾经做过的设计来试着总结一下对程序设计的理解,并试着抽取一下关于程序设计的方法或原则。
闲话少叙,这篇文章从什么是面向对象、面向对象的三大特征、如何进行对象的抽取设计以及面向对象的原则四个方面来说一下我理解的面向对象。
什么是面向对象
前段时间部门在开职责划分工作会的时候总工说。你们都是开发转的管理,都理解什么是面向对象编程。什么是面向对象编程哪?无非就是封装继承多态,咱们这次做职责划分就是一次封装。部门层面把每个职位做一个概括说明这就是封装,你们各个生产线再根据自己的实际需要选择自己需要的职位职责详细说明并细化到每个人这就是继承和多态。如果那个部门项目紧或者人员进行跨生产线调动我的要求就是就位以后很快就能有符合公司要求的产出,这个就是可替换原则。
确实是这样的,面向对象与其说一种工具不如说是一种思考方式,是用程序设计的眼光看待现实的世界。把现实世界的事物按照一定的方法和规则在不同层次上进行抽象,在抽象的时候我们只需要关注事物的核心属性和行为而忽略其他的细节。开发人员将这些属性和方法再体现在一个个的模块、类或者子程序里面,通过他们之间有序的调用,最终完成现实世界在计算机里的构造。这里的不同层次是指在不同的视角下,比如上面的职责划分从单个员工的层面来说可能需要考虑个人的现有能力,发展意愿、潜力等,如果从生产线的层面来说就需要考虑生产线产品对技术的要求,人员的配置等,从部门的层面来说可能就需要考虑成本、产值等其他的事情了。如果对应到编程实现上可能就是需要在子程序接口、类接口以及模块接口这些层面进行考虑了。
面向对象的三大特征
面向对象的三大特征是封装性、继承性、多态性。但是我认为继承和多态应该是因果的关系,因为面向对象可以继承所以有了多态。这里就把继承和多态放到一块来说一下。
先说封装,封装就是隐藏细节,让使用者只看到被允许看到的,不能看到任何实现的细节。那么如何去把握那些可以被允许那些是细节那,或者换个说法就是如何进行有效的封装那?有一个最简单的办法就是先把可访问性降一级看看能不能实现功能。比如把public 变为protected或者private试试,如果可以那就降。另外就是不要暴露成员数据,对于需要暴露的也优先考虑只提供读接口。如果觉的这个也不好把握的话,那就采用接口+实现类的编程方式。但是我觉的这样会增加很多多余的文件而且也有点偏离接口的用途(下结会详细说一下接口和抽象类),所以我建议对于模块来说只对外提供一个接口或一组小的接口,内部实现类的话设置为库内(C#)或包内(Java)可见就行了,不用专门的创建一个接口(这种情况只适用于一个人开发的模块,多人合作的话建议对模块再次细分,直到可以明细化到每个人去实现)。
另外还有一个需要注意的地方就是不要去假设封装对象的使用者,只对上下文(模块)或参数(类:如果类所属模块有上下文的话建议类的参数也要是上下文)负责,这样也可以尽量减少对象与对象之间的耦合度。
再说一下继承,封装可以是模块层面的也可以说是类层面的。但继承的话一般就是指类与类之间的一种特殊关系了。他的作用就是一个类对另一个类的细化,他们拥有共同的接口、一部分共同的数据成员和内部实现。继承能够把这些放到一个基类里面,避免了代码和数据的重复。本人并不太赞同普通类之间的继承。一般优先考虑接口的继承,其次考虑抽象类的继承,实在没有别的选择情况下再考虑普通类之间的继承。在使用继承的时候也要注意下一些事情,第一个就是继承的类要全部实现基类的接口。第二个就是对于能抽取的方法都抽取到基类里面。最后一个也是最重要的一个就是不要用多重继承。另外如果一个基类只有一个实现类的话就不要继承了,在一个类里面实现就行了。那么在什么情况下会建议使用继承,就是在类里面有太多的swith case或者 else if的时候就可以考虑继承了。
基类被子类继承后,每个子类都是基类的一个形态,这种不同形态就是多态。上面说到“如果一个基类只有一个实现类的话就不要继承了”。所有也可以说只要有继承就有多态。
如何进行对象的抽取设计
上面讲到了面向对象以及面向对象的三大特征。那么在我们实际运用中如何按面向对象的方法对事物进行抽象和构建。我认为应该考虑以下的一些步骤。在第二段的时候说程序设计是一个反复迭代不断试错的过程。所以这些步骤只是一个建议,不必按特定的顺序来,他们也可能被反复的使用。相信每个成熟的程序员都会有自己一套成熟的设计思路的。步骤共分为两个部分识别现实的对象以及用程序构造对象。
1、从不同层次对现实世界进行抽象,提取出他们的核心属性以及核心行为。
2、确定对象之间的功能边界以及依赖关系。
3、定义对象的核心属性(数据)和操作(方法)。
4、定义对象的公有和私有部分。
5、定义接口及其所需要的参数,并抽取成上下文。
6、定义主要子程序及其实现细节。
面向对象的部分原则
古代官场将做事要三思而行,三思就是思危,思变,思退,凡事从这三个方面综合考虑最终来决定如何应对将要处理的事情。同样我们在进行面向对象编程的的时候也有一些可以参考的标准。实际开发的时候需要根据这些标准权衡利弊以后再决定如何进行设计开发。同样的道理当我们去判断一个程序一个模块或一个类好坏的时候也是要从这些标准来进行考虑判断。面向对象有六大设计原则在这里我只捡比较重要的四个来说一下。另外两个在核心思想上和这四个有些重复,所以就略过不提了。
1、单一职责原则:这里不能把它理解为只做一件事情,而是要结合上面对象的功能边界来说,应该是只做对象功能边界内的事情。功能边界的选择决定了封装的质量,边界太大了容易造成对象内聚性的减弱,而范围太小又容易造成系统对象过多进一步造成系统耦合性过高。
2、开发封闭原则:这里的开放是指对对象的操作方法修改的开放,当有新的需求变化就可以对现有的代码进行修改和扩展。封闭是有两个方面,一个是范围的封闭,一但对象的范围确定了就不能再进行任何修改了,另外一个封闭是指对接口的封闭,可以慎重的进行接口的增加,但是对于已发布的接口修改是要尽力避免的或者是绝对禁止的。
3、里氏替换原则:它的核心思想就是 对于基类中定义的所有子程序,用它的任何一个子类时他们的接口含义都应该是相同的。比如一个FileDownload基类以及FtpDownload、ShareDownload、HttpDownload三个子类。使用者应该能够任意调用三个子类里的download接口来进行文档下载。
4、接口隔离原则:它的核心思想是不要定义一个大的接口,而要使用一组小而专业的接口。看定义貌似是和单一职责原则有点冲突,其实他们是从不同的角度来对抽象的审视,单一性原则是从封装的角度保证对象的纯洁性,而接口隔离原则是从对外开放的角度来考虑,让使用者只使用可以使用的那一部分功能。比如:对一件代售的商品而言,作为买方只需要查看他的价格,但是对于店主来说除了可以看价格以外还可以修改它的价格,查看和修改都是对这一个对象的操作,但对于不用的用户来说就有不同的操作权限了。