写了很多代码,碰到过很多项目。我发现在一些小型一点的项目中面向对象可以达到很好的效果,但是在一些大型一些的项目中却很难大面积推行,我想了一下可能有下面的原因。
面向对象的开发理念缺乏理论支持和工程指引。
估计任何一个大学学习计算机或者相关专业的学生都学习或者接触过面向对象。但是在学校课程中给到学生的无非就是三大特性,如果碰到好一点的老师可能就会提及一下SOLID原则。在企业中做项目,关于如何对需求进行抽象和设计很多时候都是技术员各自发挥,而很少有想过引进相关理论去做指引,这样很有可能最后导致就是大型项目无法实现好的抽象从而提高整体的质量。
抽象代码难以被大多数技术人员所理解
在大型项目里面总有一些同事是能够很好的对需求进行抽象并且表达成代码的。但是很多时候大型项目都需要代码可以被任何其他程序员理解和维护。这样抽象了的代码就不能很直接的被其他程序员所理解,在一定程度上面提高了项目管理的难度,这是对系统进行抽象不被项目管理青睐的其中一个原因。
需要对于系统抽象的注解和说明
对于同一件事情,一千个人有一千种看法,对于同一个需求,一千个程序员会有一千种抽象的方式。这种抽象的方式必须要通过有效的方式记录下来,在开发之前让同Team的同事大家都理解和认可了这种抽象方式,这样在后面开发和维护的时候才能让团队有一致的认识以及可以大家一起贡献代码。所以UML,设计以及设计的宣讲在团队里面十分重要。
硬性地对真实事物的映射
在小型项目,因为需要处理的逻辑不复杂,按照真实事物来搭建模型还是可行的。但是在大型系统里面,要处理的事物十分复杂,如果尝试用代码映射整个真实事物会把代码带来大量无谓的复杂性。对象模型只能是对于真实世界事物的抽象,只要抽象的方式还是能够附和大家对该事物的理解,对象模型就应该为解决系统问题的前提下,越简单越好。
边界上下文需要管理好
同样的一个名词,在不同的子系统和不同的团队有着截然不一样的含义。比如同样的一个词产品,Product。在销售部大家关注的是他的销售额;在产品展示子系统,大家关注的是他的产品特性分类和价格;在仓管,大家关注他的库存;在售后,大家关注的是这一只产品的投诉原因。所以虽然都叫Product产品,但是它却应该在不同的边界上下文需要有重新的定义。在代码重用的问题上也应该持一种克制的态度。
需要对大系统进行划分和分类
不是整个系统都需要有很好的设计。资源永远都是有限的,所以好的资源必须用到刀刃上。对于整体的系统,必须先进行一个划分,可以分为Core核心,Supporting支撑的和Generic一般性的。我们的最好的资源应该投入到Core核心系统的开发上面,然后Supporting可以不那么看重,然后Generic的就是通用型的需求,可能也重要,但却是有通用解决方案的。下面是来自Pattern,Principles,and Practises of Domain-Driven Design的截图。对于一个类似Ebay这种以投标为主的网上交易网站,卖家管理和投标管理对他们来说是最重要,需要用最好的程序员和设计师,而产品展示,会员管理则相对而言只是Support的重要性。而售后的功能甚至可以直接外包或者购买商业解决方案。
团队没有Clean Code的追求
“代码能跑就行了,为什么要写的漂亮?”如果根本上没有追求,那么什么都谈不上了。
团队没有重构和TDD的习惯
既然有抽象,那设计和代码就是有它背后讲述的故事和要表达的意思的。所以当有新的需求,或者随着时间大家对系统有了更深一层的理解的时候,我们应该要让代码跟随改进以反映新的需求和理解,这就需要团队有重构代码的习惯,不能看着原有代码腐败还直接在上面加新功能。而重构代码要避免引进问题,测试驱动开发的实践也是必须的。
面向对象继承方法被滥用
这个问题主要出在对于代码的重用上面。如果有一个团队设计了一套对象模型出来并且在自己所在的团队取得了很大的成功,然后想把这套模型应用于其他的子系统,这个时候就需要十分的小心。对象模型的设计本身就是往高内聚,低耦合的方向形成的,特别是继承,父子关系更加是十分密切的关系,父亲或者子女的逻辑改动都可能都整体的运行结果有影响。比如下面来自
Goodbye, Object Oriented Programming的例子
import java.util.ArrayList;
public class Array
{
private ArrayList
public class ArrayCount extends Array
{
private int count = 0;
@Override
public void add(Object element)
{
super.add(element);
++count;
}
@Override
public void addAll(Object elements[])
{
super.addAll(elements);
count += elements.length;
}
}
ArrayCount是Array的子类,但从逻辑看,其实很正常,但是却引进了一个bug,就是count的数量重复统计了。可见通过继承可以大量的抽取共享代码,但是父子之间必须是用来维护高内聚的逻辑,又或者是团队内部的逻辑,而不能用来维护低耦合的调用关系或者跨团队的代码。
写了这么多,才发现,原来面向对象,对系统进行抽象还需要这么许多的条件配合好才能成行,比起学校里面教的三大特性真的复杂了许多。但是如果我们是有追求希望把事情做好的,这些也都不是不能克服的困难。有兴趣的同学可以好好学习领域驱动开发,可以了解很多大型复杂系统的落地问题。