代码改变世界

[转载]AAF灵便应用框架简介系列(7):休息一下,泛谈OOAD(面向对象分析设计)

2009-04-02 09:38  周国选  阅读(373)  评论(0编辑  收藏  举报

最近有点闲散,很久没有写文章了。明天起要认真准备一点东西,一段时间内也没什么时间动笔。看了一些朋友的留言和回复,觉得还是有必要抓紧时间将一些思路写出来与大家分享。

AAF系列还将写下去,但是AAF的一些具体实现确实是比较枯燥的。所以本篇文章同前一篇类似,还是以谈思路为主。这次我们谈一谈OOA和OOD。我不准备去按教科书的讲法介绍二者。教科书中的东西有时候象是筋被挑去的动物解剖尸体——清晰、冷酷、完整但是也没有呼吸。当然,这并不是说基本概念不重要,基本概念非常重要,但是基本概念的重要并不在于你需要将一段段文字背得滚瓜烂熟,而在于深刻的理解。理解的过程实际上是你通过反复的实践,对知识在头脑中不断建模和修订的过程。

那么怎样的理解可以说是深刻的理解呢?简而言之,那就是你对每一个知识点(方法、定理...)的适用条件、裁剪依据,相互关系、具体内容、利弊之处等都有了尽可能清晰而在你的个人经验中找得到实际支撑的认识,不再不知何时该使用,不知如何使用,也不再为了使用而使用。如果你读了一本书,这本书中所提到的各种概念所依托的事实,你以前大多没有在实际操作中接触过,短期内你也不会在具体任务(工作任务或者自己定的任务)中用到它。那我觉得你这本书基本读得没什么意义。这也就是我常说:“你用不着的东西就不要去学,如果学了就要赶紧找机会去用”。人的知识体系的生长过程其实极其类似晶体的生长过程:新的知识是围绕着旧的知识建立起来的,旧的晶体如果还没长结实,或者根本没有相关的旧知识,或者就的知识和新的知识没有找到一致的生长方向,那么,轻轻一哆嗦,堆砌起来的新的知识就会洒落一地。只有不断学习、不断实践,围绕学习实践、围绕时间学习,晶体才能按照内在的纹理和晶格方向最健康的生长,完整的晶体也才有可能形成。

可惜的是,我们的教育体系,基本上大学四年都是在让学生稀里糊涂地泡在哪里,晶体稀里糊涂的乱长者,失去了在实践中的反复推敲和反馈调整。稀稀拉拉的分子结构到了毕业就业的时候根本不成形状。问起来什么似乎都知道一点,用起来却根本不成系统。这怪不得学生,但也怪不得不喜欢应届生的企事业单位,要怪的是教育体系。

扯得远了,按照惯例,还是把我关于OOAD的一些认识列举出来:
1)OOA的实质是确定问题:系统的边界和部分细节(交互细节,而非全部细节)。

OOA的实质就是确定问题本身,此时此刻,应把系统看成一个黑盒,所有的工作都围绕确定系统边界这一点展开。一个常见的误解是觉得分析不应涉及细节(很多时候大家说“实现细节”,但很多人其实是把细节都算作实现细节的)?然而事实恰恰相反,分析并不是不涉及细节,虽然也不是涉及所有细节。分析的目的就是要确定和系统边界有关的所有细节——我们称之为交互细节。那么什么算是交互细节呢?交互细节就是系统与外部交互的所有细节。这包括:a)有哪些交互,b)分别是和谁(外部系统)交互,c)这些交互的具体过程和逻辑是怎样的,d)对这些具体过程和逻辑有什么其它(分别或者总体)要求?

在这些要求里a),b)一般不难甄别的,c)一般是关键所在,而d)则往往从属于c)。在对c)进行深入讨论之前,我们要注意到一件事情,那就是在考虑b)的时候,我们有时候(不是所有的时候)必须把需要选用的技术平台作为需要与目标系统交互的外部系统来看待。因为很多时候或者是我们自己,或者是我们的用户会对技术平台的选用提出非常具体的要求。当然,对于这部分外部系统,我们可以不必考虑其与系统的交互,因为这些系统一般都提供了对我们来说几近常识一般的API,我们所要做的只是在分析中标注这些API就够了。

下面我们来看“c)这些交互的具体过程和逻辑是怎样的”。如何回答这一问题呢?让我们以UI(UI本质上和API没什么太大区别,可以把UI看作基于人工智能+视觉/多媒体调用的API,也可以把API看作形式化、简单化并通过内存地址调用的的UI)为例来考虑这一问题。当我们在考虑UI中的一个交互时,我们需要非常细节化、非常明确地回答下述所有问题:
i)交互的名称
ii)交互的前提条件
iii)交互中传入什么数据,传出什么数据,具体内容、格式及内容的内在关系。
iv)交互的逻辑(包括何时传入或传出上述数据)及分支
v)外部系统在交互中的各个节点希望达到的实际效果或结果
vi)其它外部系统关注或者必须提交的信息。

实际上我们看到,我们这里提到的一切几乎都和UML中的概念存在着对应关系,如:
外部系统(包括各种用户)->Actor
交互->Use Case
交互->Use Case
交互的具体过程和逻辑->用例描述
...

对于有一定OOA经验的人来说,这可能有点老生常谈了。但是重要的是实效和执行:如何运用上述基本原则正确、完整、一致、无歧义、清晰地描述系统的需求,永远是一个挑战。

如果说OOA的基本问题是确定系统边界的话,OOD的基本问题就是如何对我们手中的工具(如.NET的各种类)进行组合,建立符合约定边界特征的系统。

2)OOD的实质是把责任横向分解或者说横向聚合。

既是分解又是聚合,这确实是个充满哲学气息的命题。
当我们开始讨论这一问题之前,让我们看一下,如果没有类型和分层,甚至没有方法的概念(假设我们只能把用例映射为方法,除此之外不能有其他方法。同时假设所有用例在除了依靠类似的数据之外相互独立,相互之间不存在包含、扩展等关系)。这个时候,我们实现的系统从其编程结构上看起来会非常简单:一个用例对应一个方法,方法之间没有调用关系,只不过每一个方法可能都很长,有的可能长到上万行,将从数据库或文件系统抓各种数据,分析各种数据、处理各种数据、种种判断逻辑、最终通过UI呈现给用户的过程全部包含在“一根面条”之中。很多围绕类似数据的类似代码到处都是,不但很多方法里会在不同的角落出现,即使在同一个方法中也可能多次出现,我们可以将这种系统称为“长面条系统”。当然,这样的系统维护和升级将变得非常不简单。这就是我常说的关于简单的辩证法:你要简单,那么到底是要结构简单还是要维护、扩展简单?是要看起来简单还是要改起来简单?是要这些用户用起来简单还是要那些用户管理起来简单?简单失去了约束,自己都会变得不简单。所以不要轻易说:“我就喜欢简单”。

OO就其本质是为了浓缩代码和应对变化的。实际上,很多时候,浓缩代码和应对变化所需采取的行动就其“纹理”来说一模一样。我们想象一个实际的业务组织其业务系统是如何改变的。基本上,我们不大可能因为突然有一个业务人员对你高声叫道:“喂!小王啊,你把xxx.cs这个代码文件中第28行的第18位数字从0改成1”。世界很大,我不敢保证绝没有一个人听到这样的叫声,但我想应该极其稀少,而且叫的人肯定不是一个全职业务人员,因为很显然,他肯定还兼任你们的Project Manager。

正常的声音应该是这样的“小王GG~,我们要调整佣金策略了哎,超过100现在我们每100只受1块钱了哎!你帮我们改改好不?”
OO的实质就是我们要在系统中体现出业务人员嘴里念叨的概念,因为只有这样,当业务人员的念法改变的时候,我们才容易跟着改变。因为很显然,一个稳定的组织,它可能在一次业务调整中取消了一个业务名词、新增了一个业务名词,扩展了一个业务名次。无论是哪一种情况。在围绕这些名词建立的系统中,我们都很容易几乎一一对应的找到修改的位置。否则我们就要在结构简单的“长面条系统”到处寻找相关的代码,工作量很大还容易犯错误。如果我们的系统中独立体现了佣金策略的概念,那我们的改动可能将只限于佣金策略文件的一个方法。如果我们曾经对佣金策略的结构进行过很好的抽象的话,那我们可能根本不需要改任何代码,改改配置就解决问题了。

正常企业基本都是这样进化的,不正常的当然要有。新的王处长到位了,踌躇满志啊:“以前的流程和方法不对,我要重新做!”。遇到这种变故,除非你的系统抽象到很高的层级,以至于这种变化也可以清晰被化解,否则你就节哀顺变吧。这不叫业务组织进化,这叫全球核冬天下的物种突变。

当我们明白了“OO的实质就是我们要在系统中体现出业务人员嘴里念叨的概念”之后,我们的“长面条系统”就也要开始进化了,围绕业务对象(所谓业务对象,就是业务人员或者说系统的使用人员嘴里常常念叨的概念。这个“定义”同学们一定要记住了,这是和列宁关于阶级的定义一样重要的定义:所谓阶级,就是这样一些......)。当我们按照这个思路重构我们的“长面条系统”时,对象就“产生”了,业务人员口中的名词、甚至动词(匪夷所思,Command模式就是一个很经典的例子,一个具有了提交、回滚等复杂特性的动作也可以成为一种对象)根据其在系统中承载的内容开始形变:承载内容多的就成为业务对象,承载内容少到仅限于自己的就成为属性。如果把对象的从无到有看作是细胞的分解的话,很显然,这种分解把分散在“长面条系统”中很多根面条和同一根面条中很多个地方的代码被急剧的浓缩在一起,这就是我们所说的聚合。对象的很多分化细节很大程度上依赖于你的实际业务考虑,不存在绝对的标准。但是基本的方法和大的思路是要遵守上述原则的。

经过了这一改造之后的“长面条系统”演化为“对象面条系统”.


3)分层结构的实质是把责任纵向分解或者说纵向聚合。

当我们通过对“OO的实质就是我们要在系统中体现出业务人员嘴里念叨的概念”地贯彻,将业务对象引入了“长面条系统”之后,系统的维护和扩展确实变得更容易了。但是还是我们忽略了另一个重要的因素:技术的存在。是的,有时候你们的PM也会来给你们找麻烦:“喂,小王吧,业务数据TMD增长太快了!一部分数据要搬到另一台机器了,应用的主要业务逻辑你也给我优化一下!”。

看到了吧,有时候系统确实也会按照存储、业务逻辑等纵向的形式来修改。当每一个或者很多对象都有存储或者业务逻辑的变更的时候,“长面条系统”怎样组织才能让你能够尽可能地将一个修改“动机”与一个对象对应呢?很显然,分层是答案。当我们把纯粹的业务对象模型及其业务逻辑(OOA中的“交互”在实现中的映射)和其它层面的考虑区分开来之后。其它层面可以根据自身的特点对“对象面条系统”中截取出来的本层面代码进行重构,我们就可以按照缓存、持久化等等再次浓缩逻辑实现分层结构。这样,我们就得到了“分层对象系统”。


4)本人的分层习惯,粗略描述如下:存储层,对象模型层,持久化层,通用服务层,应用服务层,UI层。

存储层一般是RDBMS了,不需要我们做什么,花钱买套数据库就行了。

对象模型层,我其实是使用AAF,基于AAF的对象模型,我可以基本不关注业务逻辑和业务对象属性至外的细节,关系等实现起来很简单。

持久化层,我也是使用AAF,通过AAF的IPersister等服务,我可以无代价的实现:可配置的缓存,跨服务器的自动缓存同步,快速加载初始化服务,自动及可配置的O-R Mapping等。

通用服务层,我还是使用AAF,AAF的特点是将业务对象模型和业务对象逻辑分开,将业务逻辑服务接口和业务逻辑服务实现分开,这对于系统的维护有很大的好处。实际上,这个层次内部还是可以进一步划分的。在通用服务层,目前我主要使用AAF提供的可扩展集中参数服务、统一组织服务,高精度授权服务等。

应用服务层是根据具体应用特点开发的,也可以在一定领域内高质量复用,具体提供哪些服务就取决于具体的项目或应用了。

UI层是最终的用户界面层。我原来基于AAF参考DotNetNuke开发了一个UI框架。因为种种原因,目前处于一个中间半成品状态。基本概念和DNN类似,只是将其对象模型和存储加载等级制根据AAF的特点进行了重写,在界面上能搬还是搬过来的。但是这部分工作量较大,我花的时间又不多,加上DNN不是基于O-R Mapping实现的,很难简单迅速的移植过来。关于这一点,最近我还会抽空思考一下决定今后的思路。无论如何按照这种做法,UI实际上可以拆分为UI逻辑层和UI框架层。前者主要管理UI及其模块等的层次结构,后者就是负责呈现UI逻辑的UI框架。UI框架根据UI逻辑生成最终的UI。