在我踏入软件行业后,一直苦于没有前辈指点。我常年困惑于两个问题:一是怎样培养面向对象设计的思维能力?二是怎样进行架构设计,有无方法? 因为我做了那么多年项目,却很少看到有漂亮的面向对象思维写出来的代码,觉得有必要提醒下年轻从业者。如今总结一下自己的经历,希望对刚刚入行的朋友有些启发吧。
我的基本观念是,面向对象的思维方式是可以循序渐进地培养出来的。通俗地讲,就是不断地编码实践,量变会引起质变。
1 开发人员的思考动力不足
记得参加工作后做的第一个项目是某电信局的运营支撑系统开发,采用的开发框架是Struts1+EJB+Hibernate的组合,WebLogic8作应用服务器。 弹指间,十年时光飞逝,慢慢适应了中年大叔的生活。 有趣的是,经典的组合搭配至今没有过时(千万不要那么快过时啊,靠它混饭吃啊),特别是在企业应用中,例如大家常说SSH组合:Struts/SpringMVC
+ Spring/EJB + Hibernate/Mybatis/JPA等。 开源框架的出现,使得只要在其基础上进行二次开发就可以大大降低开发工作量。只要高级工程师搭建好了项目的开发工程,初级开发人员就可以依照模版代码,依葫芦画瓢,流水线作业进行业务功能开发。
从项目组整体的生产效率角度看,这确实是很大的进步。各个成员不同分工,各自做好自己的一部分工作即可,也符合现代企业管理的理念。但从个体的角度看,也客观造成了一些弊端,对初级开发人员,他就是流水线长的一个螺丝工,没有机会去思考如何进行面向对象的设计。 这里我以一个常用的用户登录模块举例,系统采用经典的三层架构进行分层,类图如下:
简单解释下业务场景:
1) 用户用浏览器访问系统登录页面,输入用户名与密码,提交表单。
2) 系统对用户的用户名和密码进行验证,并对用户的访问进行日志记录以便以后作审计。其中数据库中存储的用户密码信息是加密后的字符串。
作为一个初级开发人员,开发业务功能常常只需要复制粘贴图中的6个类即可。有时甚至连PasswordEncoder类都省去。还有些项目组有开发自己的代码生成工具,甚至连复制粘贴工作都省去,只需要对工具生成的代码作少量修改即完成了开发。
回到类图,这里的LoginService和UserDao接口是否有必要定义?复制粘贴以及代码生成工具使得工作量成本很低,初级开发人员就没有动力去思考这个问题的,依葫芦画瓢完成功能,打完收工即可。
我个人的观念是有没有必要取决于具体的项目需求与人员分工。
1) 如果该业务模块由1个开发人员完成,系统不需要支持多数据库,也就是UserDao没有多个实现类的需求,则UserDao接口可以移除掉。同时如果系统只有通过数据库查询认证的可能,LoginService也没有多个实现类的需求,则也可以移除掉。
2) 如果项目组中该模块每一层都由不同的人员分工合作,则由于层次间依赖的需要,引入接口使得上一层可以更早地开始开发,也使得上一层的单元测试变得简单。在这种情况下,LoginService和UserDao接口有存在的合理性。
3) 在项目中,某些模块因为业务需要Service层和Dao层必须要有多种实现类。从代码风格一致性角度考虑,存在一个类对应一个接口的情况也是可以容忍的。这样是为了维护代码的可读性,也客观上预留了系统的可扩展性。
一般来说,LoginService和UserDao接口存在有其合理性。
这些开发框架对通用功能进行了大量的封装,其本身源码中包含了大量OOD的思想。提供给开发人员进行二次开发时,单表单的增删改查由于业务需求简单,就体现不出OOD的价值了。这在一定程度上,使得开发人员去思考OOD的动力不足。 比如MVC架构中对于控制器与视图的分离,业务模型类与Servlet API的转换这些恰恰是复杂的需要OOD抽象能力的,框架已经给你实现了。框架做的多一点,所以开发人员就轻松一点。再比如Spring中对于Java
bean的创建与管理,依赖关系的注入,基于拦截器和动态代理机制来实现的声明式事物以及日志处理,还有与其它框架的集成支持等复杂点,它都给你实现了。还有Hibernate中实体对象与关系型数据库中表的对应转换,对API调用翻译转换成SQL语句,对多种数据库语法的支持,查询结果的缓存等,也是复杂点。 反过来说,如果你不用任何框架,去实现一个中等规模的Web应用。看看自己写的代码与现在基于框架二次开发的代码差别大不大,差别在哪里。我想,自己去实现未必会比开源大牛们设计的更好,但却完全可以体会到复杂点难点在哪里,OOD是不是有应用场景,因为写的过程中蛋疼了。编程虽易,OO不易,且编且珍惜。
2 Java平台中的面向对象举例
Java语言的API规范中,可以说是处处体现了OOD。这里仅仅举Servlet和JDBC规范两个例子,不同的厂商底层对Servlet
API的实现,JDBC驱动的实现,完全对开发人员屏蔽,两套规范都实现了精炼的抽象。
Servlet API把HTTP协议中的请求信息封装成HttpServletRequest对象,响应消息封装成HttpServletResponse对象。开发人员直接从这两个对象中获取HTTP通信中的各种HTTP头信息,参数信息,以及完成对HTTP客户端的响应信息输出。
JDBC API使得开发人员可以不考虑具体的数据库类型,用相同的API完成与数据库的交互。 完全可以说,掌握了Servlet API就掌握了Java Web应用开发;掌握了JDBC API就掌握了与Java数据库应用开发。 Java开源社区奉献了大量优秀的框架,例如:Lucene,Hadoop,Hbase,Mina,Netty,ActiveMQ等在互联网和电商行业得到广泛应用。(看来搞Java一时半会不会找不到工作,不过少年你天资聪颖活力青春,我还是建议你搞IOS开发简单粗暴,不解释)。
3 面向对象不适用于所有业务场景
在Java语言中,一切都是对象。那是不是所有的业务问题,都可以用面向对象的方式去设计实现呢?要知道“尺有所短,寸有所长”,OOD也不是全知全能的宇宙真理啊! 举个例子,比如要实现一个自然整数n的阶乘。你再怎么面向对象去思维,也无法去抽象出对象模型对应这个问题。这种场景下,反而过程式的实现更加简单直接。还有很多数学推导公式的求解也是如此。 再举个例子,项目中有个实现最短路径算法的需要。虽然我用面向对象思维方式活生生写了10来个类实现了功能。网上一搜,C语言用邻接矩阵存储的方式来实现的一两个类就实现了该功能。面向对象的方式可能更加适合开发人员去读懂,对于计算机来说,可能面向过程的实现运行效率更高。 在我看来,计算机技术的本质是计算。各种二进制表示的数据,通过网络通讯进行传输,然后系统对计算的结果进行存储或通过网络返回给调用方。
我们的思维方式中不能排斥,不包容其它的设计理念。认为OOD一招打遍天下,就有点愚蠢了。这点其实挺难的,我们从小的受到的教育是同一种观念,同一种政治正确性,甚至同一种价值观,不允许有异见。经常能看到论坛上非此即彼的对骂。好在互联网的开放性,使得越来越多的人有了多元的世界观,价值观。
4 学习设计模式可帮助理解OOD
设计模式列举了一些经典业务场景的最佳实践,非常值得借鉴学习。我们学习设计模式中常用的23种招式,最终目的是培养自己对OOD的悟性。
就好像我们看武侠小说里面,十八般武艺招数全部学会,还不抵九阳神功一掌。对内功深厚的大师来说,飞沙走石一花一叶都可伤人性命。
但学武之初,扎扎马步,练练兵器拳法,还是有助于培养悟性的。
同时又不能生搬硬套为了模式而模式,觉得它精妙就想时时处处都模式了。举个邪恶点的例子,由于教育的缺失,就如在学生时代男生普遍性启蒙都是靠观摩岛国爱情动作片来领悟啪啪啪的要义一样,你要是模仿男主角把里面的每个场景每个招式都实践一遍吧,有些高难度动作会完成不了还会伤害自己,你懂的。
设计模式的精髓就在遵循开闭原则,将通用代码向父类抽取,对可变的行为抽象成接口进行封装。模式的提炼应该是水到渠成的事情。
只要平时养成面向接口编程,依赖于抽象而不是依赖于具体实现类的开发习惯。当编码实践经验达到一定的临界点后,量变引起质变,不知不觉中发现写的代码已经是运用了设计模式在里面了。大家都听说过,一万小时理论,精通一项技能往往需要持续实践一万小时以上。但凡5年以上扎实地编程实践,即使得不到高人指点,也会对OOD顿悟。
5 持续重构可帮助对抽象思维的培养
OOD的精华在于抽象,抽象,再抽象。但是每个人对于设计经验有一个积累的过程,不可能一开始就设计的非常完美,能应付项目中所有的需求。 抽象思维能力,更需要一个循序渐进的培养过程。我们不断地学习优秀开源框架的源码,学习设计模式都是一种外部手段,旨在迫使自己大脑中学会抽象思考的方式。 所面临的问题域是一个子系统,一个模块,那抽象的思维培养的是面向对象设计的能力,系统分析与领域建模的能力。放大了看,如果面临的问题域是整个系统或者多个系统,则培养的就是系统架构设计的能力。
有过一定编程实践经验的人都有过这样的经历,系统中如果有重复的代码段出现2~3次就会觉得很恶心,尤其是一大段大段上百行几乎一样的代码。因为每个人的编码能力经验不同,开发的时候很可能设计不到位。那可不可以将其进行提炼复用呢?
答案是可以,因为我们有重构(Refactor)这个法宝。 持续的重构是可以有效改进面向对象的设计的。我常常在看别人的代码时候,不自觉地帮着进行重构,这只是一种习惯。当然,必须在尊重原作者的前提下,一步步小范围内重构。
落实到细节上,难点在于类和方法的命名,类的职责划分,抽象的粒度大小适中。这些真的只能靠经验积累,去领悟理解了,没有一定的标准,什么是好,什么是不好。我觉得起码命名要清晰,易于理解,类的职责要专一,方法长度不能过长。细节方面可参照大牛Martin写的那本关于重构的圣经书。
最后,一个人对知识的理解,不是线性增长或者抛物线上升的,应该是阶梯形上升的。每上一个台阶,需要熬过一段不规则的积累沉淀期,再由外界因素的触发引起内在的觉醒才能继续到下一个台阶。以前死活不明白的事情,或许随着年龄增长,都释然了。闻道有先后,但终究会顿悟。
|