标签:
随着软件系统的复杂度不断提升,对设计的要求越来越高,那么,我们应该如何设计软件系统呢?书店中,网络上,讲述软件设计的书籍、资料数不胜数,各种高大上的概念也层出不穷,看了这许多的理论,结合自己的实践经验,有很多感悟想要表达,借这篇文章来直抒胸臆。
我们先抛开各种概念、理论,单纯从软件“拥有者”的最原始的角度来考虑,我们到底需要什么样的软件?
首先,这个软件要足够“简单”。因为只有简单,才有实现的可能,只有简单,才能降低犯错误的概率,也只有简单,才好维护。
其次,这个软件易于适应“变化”。很少有开发出来就一成不变的软件,客户的需求是不断增加、变化的,所以,软件也必须满足客户需求,不断变化。
我们的目标只有两条,是呀,很简单,可实现这2条,学问就大了,下面我们展开了详细说明。
软件要处理的业务是复杂的,那怎么简单呢?
我们人类对付复杂的事情,会怎么办——面向对象思维、分层思维。
面向对象思维,一个复杂的对象,内部可以分为几个对象,如果分出的几个对象还是太复杂,每个内部对象还可以继续拆分,这样最终,我们可以将一个复杂对象,拆分成层层包含的一系列对象,最小颗粒度的对象,会足够简单。
分层思维,做一件复杂的事情(我们称之为A),需要底层功能支持,我们称之为B层,B层会实现很多功能),B也许还是太复杂,它实现的功能无法自完成,还需要C层的支持……这样,每一层只需要考虑本层需要实现的功能,我们在分层的过程中,把实现的难度降低了。7层网络协议,就是一个很好的例子。
面向对象是做水平分割,分层是做垂直分割。怎么来区分一种分割是水平分割还是垂直分割呢?水平分割产生的多个对象,是完成多项功能的。垂直分层产生的多个层,聚焦到最上层的某个模块,是完成一项功能的。这样水平+垂直两种分割,就可以将一件复杂的事情分解得足够简单。有时候,区分水平分割、垂直分割可不是一件容易的事情。
软件系统的设计,其实就是在不断做这种分割,以使单体足够简单。很多理论,很多经验,都是为了做好分割,框架设计主要的工作,也是分割,足见这是一件不容易的事情。
需求不断变化,这是一种常态,所以软件自身不断变化,也就是一种必须满足的要求。
满足变化,最重要的是什么?速度、代价。对,我们都希望,在需求增加,或者改变时,我们可以迅速,并且以最小代价满足。这就是软件设计要实现的另外一个目标。
如何迅速,并以最小代价满足变化呢?这就要提到软件工程经常说的那条经典要求:低耦合、高内聚。
前述,我们已经将复杂软件系统分解为足够简单的许多模块。一个变化来临,我们要分析,哪些模块需要改变。如果这些模块相互之间的耦合(关系)最小化,那我们修改起来就简单多了,只需要针对那些功能上需要改变的模块,展开工作就好了。可是,如果模块之间的耦合很复杂,我们改了一个模块,就一定要关注和它有关系的其他某块,这个工作量就成本增加了。在满足变化的过程中,往往,我们用于功能改变的工作量是很小的,我们绝多大数的工作量,用于“忍受”耦合。
高内聚其实也是一个道理。高内聚,换句话说,就是一个模块只实现一个功能,如果一个模块实现多个功能,我们只需要改变其中的一个,那么,我们可能影响到模块的其他功能,这其实说的是模块内部的耦合。
那么多的框架、理论,其实就是在做这件事情:低耦合、高内聚。
为了满足“简单”、“变化”的目标,软件设计实现“分割”和“低耦合、高内聚”,所以可以说,分割和低耦合、高内聚是软件设计实现层面的目标。
面向对象的思维方法,是实现“分割”最有效的方法。
高内聚没什么好说的,我们主要谈低耦合。下面我们谈谈低耦合思想发展的几个阶段:
首先,大家想到让模块之间的联系尽可能减少,最好减到只有必要的联系,去除掉一切不必要的联系。迪米特法则(最少知识原则)就是基于这种思想。面向对象思想中类的种种设计,也体现出这种思想。
接着,大家发现,虽然模块间联系已经大为减少了,只保留了必要的联系,但耦合的影响还是很大,具体来说,由于一个模块和另外一个模块有联系,那么一个模块修改了,还是可能导致另外一个模块受到影响。于是,大家想到,将这种模块间的联系,固定下来,这就产生了接口。接口没有任何实现,它只是代表了两个模块之间约定好的联系。当接口固定下来后,一个模块的变化,不会影响到与其通过接口联系的其他模块。这就好像油缸和发动机,它们直接的接口是早就约定好的,固定的,油缸和发动机各自的变化、改善可以独立完成,相互之间不会造成影响。这就是面向接口编程的思想,依赖倒置原则,spring中的控制反转思想,都是基于这种面向接口编程的思想。
到这里,耦合减少了,由于通过接口来耦合,耦合产生的影响也大幅降低,但还有一个问题,按照传统的设计,在实现层面,产生耦合的模块间,还是要相互认识。换句话说,模块A通过接口和模块B联系,在实现层面,它还是至少要知道模块B的存在吧,因为在传统设计中,模块A可能需要创建模块B的实例。这样,就造成了模块设计的侵入性,就是说,模块B侵入了模块A,这样,模块A以后无法直接移植到其他系统中使用,还是需要做一些修改。这种侵入性,其实也是一种耦合,它不是直接通讯的耦合,它是一种认识的耦合。最好,让所有模块都相互不认识,但那样,模块间的联系又如何建立呢?早期有一种办法,就是建立中间对象,这些对象不做任何具体的功能性的事情,它只负责建立模块间的联系。传统做法,父对象(高层模块)充当子对象(底层模块)的中间对象,为子对象(底层模块)间建立联系。这种方法,很好的体现了面向对象思想,同层对象(模块)间的耦合降到最低。但这种方法,也有其弊端,就是父对象要认识子对象,而且这种耦合,是侵入到代码中的,造成了父对象可移植性变差。试想,如果欲将一个父对象移植到其他项目中,并且要匹配其他的子对象,还是不得不进行对父对象进行代码修改的。
Spring为这个问题,提供了很好的解决方案,那就是控制反转。Spring中,可以在代码之外的配置文件中,为系统中各层次的父对象(上层模块)与子对象(下层模块)间建立联系,还可以为各对象提供初值设置。这简直可以称之为上帝模式,整个系统中,各层面的对象(模块)都相互不认识,它们之间的耦合降到最低,它们之间的联系,由上帝(程序员)建立。程序员甚至可以不改变任何一行代码,重新建立它们之间的联系。这种将耦合降到最低,将模块之间的联系提供给上帝(程序员)来管理的模式,理论上,可以用最低成本来应对变化。这好比,汽车中的各种复杂模块,其中又层层嵌套,包含各种更小的模块,但它们之间谁都不认识谁,它们之间的联系,是有上帝(人)来决定的。
到这里,要说到一个技术之外的话题了。上帝(人)有权决定汽车中各种复杂模块的关系,可他怎么能做到这一点呢?他要有图纸,了解模块之间的关系,他也需要有手册,了解每个模块特性。Spring为我们提供了很好的方法,使我们有机会做上帝,可以往往,呈现在我们面前的,只有一辆庞大的汽车,其中有各种复杂的模块,繁多的配置信息,但没有提供给我们图纸,也没有手册,我们从何下手呢?真的把我们当做上帝了?这就是很多项目的现状,技术是好的,初衷是好的,可是,由于没有图纸,没有手册,我们只能低下头来,一点一点分析每个模块的代码,最终,我们付出的代价,可能比维护一个技术“低级”的系统更大。所以,技术的提升,不代表可以不写文档,可以不做设计,可以降低管理。事实上,它们可以是相互促进的,好的管理,可以让开发者有更多的时间研究技术,好的技术,也可以为管理降低成本。
标签:
原文地址:http://www.cnblogs.com/chentqjl/p/4556716.html