免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 6091 | 回复: 19
打印 上一主题 下一主题

敏捷思维 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2003-08-27 08:19 |只看该作者 |倒序浏览
敏捷思维-架构设计中的方法学(1)
从方法论看架构设计
--------------------------------------------------------------------------------

来自:IBM China 作者:林星 [2003/03/27]
方法论对软件开发而言意味着什么?我们如何看待软件开发中的方法论?方法论能够成为软件开发的救命稻草吗?在读过此文后,这些疑惑就会得到解答。
在第一篇文章中,我们来了解标题中的一些词的含义。 方法学是什么?
敏捷是什么?
为什么讨论架构?
方法论
方法论的英文为Methodology,词典中的解释为"A series of related methods or techniques"我们可以把它定义为软件开发(针对软件开发)的一整套方法、过程、规则、实践、技术。关于方法论的出现的问题,我很赞同Alistair Cockburn的一句话,"方法论源于恐惧。"出于对项目的超期、成本失控等等因素的恐惧,项目经理们从以前的经验出发,制定出了一些控制、监测项目的方法、技巧。这就是方法论产生的原因。
在Agile Software Development一书中,作者提到了方法论的十三个要素,基本能够函盖方法论的各个方面:
角色(Roles)
个性(Personality)
技能(Skills)
团队(Teams)
技术(Techniques)
活动(Activities)
过程(Process)
工件(Work products)
里程碑(Milestones)
标准(Standards)
质量(Quality)
工具(Tools)
团队价值(Team Values)
它们之间的关系可以用一幅图来表示:
图 1. 方法论的十三个要素     


很多的方法论,都涉及了上面列举的十三要素中的部分要素,因此,我们可以把方法论看作是一个抽象的、无穷的超集,而现实中的方法论都是指超集的一个有限的子集而已。它们之间的关系就好像有理数和1到100之间的整数的关系一样。不论是XP,还是UI设计经验之类,都属于方法论的一个子集,只是这两个子集之间有大小的差别而已。我们还应该看到,讨论一个完备的方法论是没有意义的,因此这种方法论铁定不存在,就好像你视图穷举出所有的有理数一样荒唐。因此,我们关于一个通用的方法论的说法也是无意义的。好的方法论,比如说XP、水晶系列,它们都有一个适合的范围,因为它们了解一点,自己并不是一个无所不能的方法论。
在现实中,我们其实不断的在接触方法论。比如说,为了控制项目的进度,项目经理要求所有的开发人员每周递交一份详细的进度报告,这就是一种方法、一种技巧。如果把开发过程中的这些技巧系统的组织起来,就能够成为一种方法论。你可能会说,那一种方法论的产生也太容易了吧。不,这样产生的方法论并没有太大的实用价值,没有实用价值的方法论根本就没有存在的必要。因此,一个成功的方法论是要能够为多个的项目所接受,并且能够成功实现软件的交付的方法论。
我和我的同事在实践中做了一些试验,希望能够把一些好的方法论应用于开发团队。试验的结果很无奈,方法论实施的效果并不理想,一开始我们认为是方法本身的原因,到后来,我们发现事情并不是这么简单。在试验的过程中,开发人员一致认同方法论的优势所在,但是在实施过程中,鲜有坚持的下来的。在Agile Software Development中,我发现作者遇到了和我们一样的问题。
Alistair Cockburn在和大量的项目团队的访谈之后,写成了Agile Software Development一书。在访谈之前,他笃定自己将会发现高度精确的过程控制是成功的关键所在,结果他发现事实并非如此,他把他的发现归结为7条定律。而我在实际中的发现也包含在这七条定律中,总结起来就只有两点:沟通和反馈。
只要能够保证良好的沟通和即时的反馈,那么开发团队即使并没有采用先进的方法论,一样可以成功。相反,那些"高质量"的团队却往往由于缺乏这两个因素而导致失败(我们这里指的失败是用户拒绝使用最终的软件)。最有效,而成本也最低的沟通方法就是面对面(face to face)的沟通,而随着项目团队的变大,或是另外一些影响因素的加入(比如地理位置的隔绝),面对面的沟通越来越难实现,这导致沟通的的成本逐渐加大,质量也慢慢下降。但这并不是说非面对面的沟通不可,重要的是我们需要知道不同的沟通方式的成本和质量并不相同。XP方法尤为强调面对面的沟通,通过现场客户、站立会议、结对编程等方式来保证沟通的有效。在我的经验中,一个开发团队其实是需要多种沟通方式的结合的。完全的面对面的沟通对某些团队来说是很难实现的,那么问题的关键就在于你如何应用沟通的方式来达到你希望的效果。在前不久结束的欧莱雅创业计划大赛上,有一支团队特别引人注目,他们彼此间素未谋面,仅仅凭借Internet和电话完成了高效的合作。他们虽然没有使用面对面的沟通方式,但是仍然达成了既定的目标。软件开发也是一样的,面对面的沟通是非常有必要的,但其它的沟通方式也是需要的。
再看反馈,不论是控制进度,还是保证客户的满意度,这些活动都需要管理成本。软件开发中的管理成本的一个通性就是伴随有中间产出物(intermediate delivery)。比如说我们的需求规约、分析文档、设计文档、测试计划,这些都属于中间产出物。中间产出物的增加将会带来效率下降的问题,因为开发人员的时间都花在了完成中间产出物的工作上,花在给软件新功能上的时间就减少了。而中间产出物的主要目的是两个,一个是为了保证软件如客户所愿,例如需求规约;另一个是为了作为团队中的其他成员工作的输入,例如开发计划、测试计划等。因此,我们也可以针对这两点来商讨对策,一种是采用迭代的思想,提高软件发布的频率,以保证客户的需求被确实的满足,另一种就是缩小团队的沟通范围,保证成员能够从其他人那里得到新的思路,而不是撰写规范的内部文档(内部文档指那些仅为内部开发人员之间的沟通所需要的文档)。
因此,一个软件项目的成功和你采用的开发方法论并没有直接的关系。
重量
我们根据把拥有大量artifact(RUP官方翻译为工件,意思是软件开发过程中的中间产物,如需求规约、设计模型等)和复杂控制的软件开发方法称为重型(Heavy Weight)方法,相对的,我们称artifact较少的方法为轻型(Light Weight)方法。在传统的观念中,我们认为重型方法要比轻型安全许多。因为我们之所以想出重型方法,就是由于在中大型的项目中,项目经理往往远离代码,他无法有效的了解目前的工程的进度、质量、成本等因素。为了克服未知的恐惧感,项目经理制定了大量的中间管理方法,希望能够控制整个项目,最典型的莫过于要求开发人员频繁的递交各种表示项目目前状态的报告。
在Planning XP一书中有一段讨论轻重型方法论的精辟论述,它把重型方法论归结为一种防御性的姿态(defensive posture),而把轻型方法论归结为一种渴望成功(Plan to win)的心态。如果你是采用了防御性姿态,那么你的工作就集中在防止和跟踪错误上,大量的工作流程的制定,是为了保证项目不犯错误,而不是项目成功。而这种方法也不可谓不好,但前提是如果整个团队能够满足前面所提到的两个条件的话,项目也肯定会成功,但是重型方法论的一个弊端就在于,大家都在防止错误,都在惧怕错误,因此人和人之间的关系是很微妙的,要达到充分的沟通也是很难的。最终,连对人的评价也变成是以避免错误的多寡作为考评的依据,而不是成就。我们在做试验的时候,一位项目经理开玩笑说,"方法论源自项目经理的恐惧,这没错。但最糟糕的是整个团队只有项目经理一个人恐惧,如果能够做到人人的恐惧,那大家也就没有什么好恐惧的了。"这句话提醒了我们,如果一个团队的精神就是力求成功,那么这支团队的心态就和其它的团队不同了,尤其是对待错误的心态上。根本就没有必要花费大量的精力来预防错误,错误犯了就犯了,即时改正就可以了。这其实就是渴望成功的心态。
方法论的艺术
管理,被称为科学和艺术的融合体,而管理的艺术性部分很大程度的体现为人的管理上。我说,方法学,一样是科学和艺术的融合体。这是有依据的,其实方法论和管理学是近亲关系,管理学中有一门分支是项目管理,而在软件组织中,项目管理是非常重要的,方法学就是一种针对软件开发的一种特定的项目管理(或是项目管理的一个子集)。
重型方法最大的一个问题就在于他不清楚或忽略了艺术这个层次,忽视了人的因素,把人做为一个计量单位,一种资源,一种线性元素。而人的要素在软件开发中是非常重要的,软件开发实际上是一种知识、智力的转移过程,最终形成的产品是一种知识产品,它的成本取决于开发者的知识价值,因此,人是最重要的因素。而人这个要素是很难衡量的,每个人都有不同的个性、想法、经验、经历,这么多复杂的因素加在一起,就导致了人的不可预见性。因此,我们强调管人的艺术。
最简单的例子是,在重型方法中,我们的基本假设是对人的不信任。项目经理要控制项目。但不信任就会产生很多的问题,比如士气不高,计划赶不上变化,创新能力低下,跳槽率升高等等。人都是希望被尊重的,技术人员更看重这一点,而很多公司也口口声声说自己多么多么以人为本,可是采用的却是以不信任人为前提的开发方法,言行不一。我们说敏捷方法的出发点是相互信任,做到这一点是很难的,但是一旦做到了,那这个团队就是非常具有竞争力的。因此,这就产生了一个问题,在没有做到完全的相互信任之前,我们到底相不相信他人呢,这就是我提到的艺术性的问题,什么时候你要相信人?什么时候你不相信人,这些都是需要权衡的问题,也都是表现你艺术性的问题。
敏捷
敏捷代表着有效和灵活。我们称那些轻型的、有效的方法为敏捷方法。在重型方法中,我们在一些不必要、重复的中间环节上浪费了太多的精力,而敏捷则避免了这种浪费。我们的文章将会重点的讨论敏捷(Agile)方法论的思想,敏捷这个名字的前身就是轻型。目前已经有了一个敏捷联盟,他们制定了敏捷宣言:
Individuals and interactions over processes and tools.
Working software over comprehensive documentation.
Customer collaboration over contract negotiation.
Responding to change over following a plan.
而我对敏捷的理解包括了几个方面:
较低的管理成本和高质量的产出。软件开发存在两个极端:一个是没有任何的管理成本,所有的工作都是为了软件的产出,但是这种方式却往往导致软件开发过程的混沌,产品的低质量,团队士气的低落。另一个是大量管理活动的加入,评审、变更管理,缺陷跟踪,虽然管理活动的加入能够在一定程度上提高开发过程的有序性,但是成本却因此提高,更糟糕的是,很容易导致团队的低效率,降低创新能力。因此,敏捷方法视图寻找一个平衡点,用低成本的管理活动带来最大的产出,即软件的高质量。
尊重人性。敏捷方法尊重人性,强调效率。软件开发可以说是一种脑力的投入,如果不能保证开发人员的自愿投入,产品就肯定要打折扣。事实多次的证明,一个愿意投入的开发人员和一个不愿意投入的开发人员效率相差在三倍以上,对组织的贡献更是在十倍以上。
沟通和反馈是一切的基础。我们已经讨论过沟通的重要程度,而即时的反馈是拥抱变化的前提条件。
客户是上帝。没有客户就没有一切,客户的重要性可以用一句话来形容,就是以合理的成本建造合适的软件(build the right system at the right cost)。
敏捷其实也有轻重之分,关键在于是否能够做到有效和灵活。因此,敏捷方法论提倡的一个思想是"刚好够(barely sufficient)"。不过这个"刚好够"可不是那么容易判断的。一支8个人的团队采用XP方法,随着方法的熟练使用,团队的能力在不断的增强,能够处理的问题越越来越复杂,也许他们能够处理采用重型方法的20个人团队能够处理的问题。可是如果团队的人数突然增加到12人,这支团队肯定就会出问题,他的表现可能还不如那支20个人的团队了。人数增加了的时候,原先的方法肯定还做适当的调整,比如说,在原先的敏捷方法上增加一些重型方法的技巧。我们不能够要求一支6个人的团队和一支20个人的团队用同样的方法,前者可能采用轻一些的敏捷方法,后者可能采用重一些的敏捷方法,关键的问题在于,两支团队都把重点放在沟通、反馈、频繁交付软件这些关键的因素上,也就是做到有效和灵活。
架构设计
架构(Architecture)(也有被称为体系结构的)是软件设计中非常重要的一个环节。软件开发的过程中只要需求和架构确定之后,这个软件就基本上可以定型了。这就好比骨骼确定了,这个人的体形就不会有很大的变化。因此我选择了架构设计来讨论敏捷软件开发(需求我已经写过了)。我们在前面讨论过超集和子集的概念,因此我们接下去要讨论的架构设计也是一个很小的子集。方法论如果没有经历过多个项目的检验是不能称为成功的方法论的,我也并不认为我的架构设计就是一个好的方法论,但引玉还需抛砖,他的主要目的是为了传播一种思想。因此,我采用了模式语言(PLOP)做为写作架构设计的形式,主要的原因就是模式是一种很好的组织思想的方法。
因此,在我们接下去的历程中,我们集中讨论的东西就围绕着架构、方法学、敏捷这三个要素展开。这篇文章并不是讨论如何编码实现软件架构的,也不要单纯的把它看作架构设计的指南,其实文中的很多思想来自于方法论,因此提到的很多架构设计的思想也适用于其它工作,如果能够了解这一点,看这篇文章的收获可能会更多一些。

No057-1.jpg (34.96 KB, 下载次数: 86)

图 1. 方法论的十三个要素

图 1. 方法论的十三个要素

论坛徽章:
0
2 [报告]
发表于 2003-08-27 08:25 |只看该作者

敏捷思维

林星的观点是还有点意思,应该说非常实用!

论坛徽章:
0
3 [报告]
发表于 2003-08-27 08:32 |只看该作者

敏捷思维

敏捷思维-架构设计中的方法学(4)
团队设计
--------------------------------------------------------------------------------

来自:IBM China 作者:林星 [2003/04/02]
团队设计是敏捷方法论中很重要的一项实践。我们这里说的团队,指的并不是复数的人。一群人就是一群人,并没有办法构成团队。要想成为团队,有很多的工作要做。
我们之所以考虑以团队为单位来考虑架构设计,是因为软件开发本身就不是一件个人的事情,架构设计更是如此。单个人的思维不免有考虑欠妥之处,单个人的学识也不可能覆盖所有的学科。而组织有效的团队却能够弥补这些缺憾。
Context
谁来负责架构的设计?
Problem
在我们的印象中,总认为架构设计是那些所谓架构设计师的专属工作,他们往往拥有丰富的设计经验和相关的技能,他们不用编写代码,就能够设计出理论上尽善尽美的架构,配有精美的图例。
问题1:理论上设计近乎完美的架构缺乏程序的证明,在实际应用中往往会出这样那样的问题。
问题2:设计师设计架构带有很大的主观性,往往会忽视客户的需求,导致架构无法满足需求。
问题3:实现的程序员对这种架构有抵触的情绪,或是因为不理解架构而导致架构实现的失败。
问题4:架构师设计架构主要是依据自己的大量经验,设计出的架构不能真实的反映目前的软件需要。
Solution
团队设计的理论依据是群体决策。和个人决策相比,群体决策的最大好处就是其结论要更加的完整。而群体决策虽然有其优点,但其缺点也是很明显的:需要额外付出沟通成本、决策效率低、责任不明确、等等。但是群体决策如果能够组织得当的话,是能够在架构设计中发挥很大的优势的。
例1:在XP中,我们基本上看不到架构设计的影子。并不是说采用XP技术的团队就不需要架构设计。XP不存在专门的设计时期,它提倡使用一些简单的图例、比喻的方式来表达软件的架构,而这种的架构设计是无时无刻不在进行的。其实,XP中的设计采用的就是团队设计的方式,结队编程(Pair Programming)和代码的集体所有制(Collective Ownership)是团队设计的基础,也就是基于口述的沟通方式。通过采用这样的方式,XP几乎不需要文档来表达架构的设计。  

避免象牙塔式的架构设计
对软件来说,架构设计是一项至关重要的工作。这样的工作交给某个人是非常危险的。即便这个人再怎么聪明,他也可能会遗漏部分的细节。组织有效的团队的力量是大大超过个人的力量的,因此团队的成果较之个人的成果,在稳定性和思考的周密程度上,都要更胜一筹。
Scott W. Ambler在其著作中给出了象牙塔式架构(ivory tower architecture)的概念:
An ivory tower architecture is one that is often developed by an architect or architectural team in relative isolation to the day-to-day development activities of your project team(s).
中国现在的软件开发行业中也逐渐出现了象牙塔式的架构设计师。这些架构师并不参与实际的程序编写,他的工作就是为项目制作出精美的架构模型,这种架构模型在理论上是相当完美的。
优秀的架构师能够充分的利用现有框架,减少软件的投入,增强软件的稳定性。这些都没有错,但是问题在于“过犹不及”。象牙塔式架构师往往会出现文章开始指出的那些问题。架构设计其实并不是非常复杂的工作,但它要求开发人员具备相关的技能、经验以及对问题域有一定的了解。开发人员往往都具有相关的技术技能(编程、数据库设计、建模),而对问题域的理解可以从用户和行业专家那里获得帮助。因此,在理论上,我们要实现架构设计的团队化是完全可能的。
在上面的象牙塔式架构定义中,我们看到架构师和日常的开发工作是隔绝的。这样的设计出的架构有很大的局限性。在现实中,我们还会发现另外一种角色,他来自于开发团队外部,为开发人员提供相关的技术或业务的培训。这种角色称为教练,在软件开发中是非常重要的角色,不能够和象牙塔式架构设计师之间画等号。
选择你的设计团队
软件的架构在软件的生命周期的全过程中都很重要,也就是说,软件开发团队中的所有人员都需要和架构打交道。因此,最好的团队组织方式是所有开发人员都参与架构的设计,我们称这种方式为全员参与。全员参与的方式保证了所有开发人员都能够对架构设计提出自己的见解,综合多方面的意见,在全体开发人员中达成一致。这种方式尤其适合于一些小的团队。
还是会有很多的团队由于种种的原因不适合采用全员参与的方式。那么,组织优秀的开发人员组成设计组也是比较好的方式。一般,我们选择那些在项目中比较重要的,有较多开发经验,或是理论扎实的那些人来组成设计组。当然,如果你考虑到为组织培养后续力量,你也可以让一些新手加入设计组,或是你觉得自己的开发力量不足,邀请外部的咨询力量介入,这完全取决于具体的情况。
设计组不同于我们之前提到的象牙塔式架构设计师。设计组设计出来的架构只能称为原始架构,它是需要不断的反馈和改进的。因此,在架构实现中,设计组的成员将会分布到开发团队的各个领域,把架构的思想带给所有开发人员,编写代码来检验架构,并获得具体的反馈,然后所有的成员再集中到设计组中讨论架构的演进。
例2:敏捷方法非常注重的就是团队的沟通。沟通是一个很有意思的话题,讲起来会花费大量的时间,我们这里只是针对架构设计中可能存在的沟通问题做一个简单的讨论。我们这里假设一个讨论情境,这个情境来源于真实的生活:
项目主管徐辉、设计师李浩、设计师罗亦明正在讨论一个新的软件架构。
"李浩你认为这个软件数据库连接部分应该如何考虑?"徐辉问。
李浩想了想,"我觉得方案A不错…" "方案A肯定有问题!这个软件和上一次的又不同。"罗亦明打断了李浩的发言。
"你懂什么!你到公司才多久,方案A是经过很长时间的证明的!"发言被打断,李浩有点恼火,罗亦明进入公司没有多久,但在一些事情上老是和他唱反调。
"我进公司多久和方案A的错误有什么关系!"
在这样一种氛围中,会议的结果可想而知。  

团队设计中存在的问题
在团队设计的过程,我们会遇到各种各样的问题,首当其冲的就是沟通成本的问题。架构设计时,需求尚未被充分理解,软件的设计思路还处于萌发的状态。这样的情况下,团队的每位成员对软件都有独特的见解,这些可能有些是相同的,有些是互斥的。就好比盲人摸象一样,他们的观点都代表了软件的一部分或是一方面,但是没有办法代表软件的全部。
在敏捷方法论中,我们的每一个流程都是迅速进行、不断改进的。架构设计也是一样,我们不可能在一次架构设计上花费更多的时间。而团队决策总是倾向于较长的讨论和权衡。
例2中的问题在架构设计中时有发生,纯技术的讨论很容易上升称为争吵。这种情况几乎没有办法完全避免。团队型的决策必然会发生观念的冲突。控制一定程度内的观念的冲突对团队的决策是有益,但是如果超出了这个程度就意味着失控了,需要团队领导者的调节。而更重要的,我们需要注意沟通的技巧:
团队沟通
团队进行架构设计的时候沟通是一个非常需要注意的问题,上述的情境在软件组织中是经常发生的,因为技术人员很自然认为自己的技术比别人的好,如果自己的技术受到质疑,那怕对方是抱着讨论的态度,也无异于自身的权威受到了挑战,面子是无论如何都需要捍卫的。而沟通如果带上了这样一层主观色彩,那么沟通信息的受众就会潜意识的拒绝接受信息。相反,他会找出对方话语中的漏洞,准备进行反击。因此,我们要注意培养一种良好的沟通氛围。
在实际的观察中,我发现团队沟通中存在两种角色,一种是建议者,他们经常能够提出建议。一种是质疑者,他们对建议提出否定性的看法。这两种角色是可能互换的,现在的建议者可能就是刚才的质疑者。质疑者的发言是很能打击建议者的积极性的,而在一个脑力激荡的会议中,最好是大家都能够扮演建议者的角色,这就要求沟通会议的主持者能够掌握好这一点,对建议给予肯定的评价,并鼓励大家提出新的建议。
良好的沟通有助于架构设计工作的开展。一个成员的能力平平的团队,可以藉由良好的沟通,设计出优秀的架构,而一个拥有一个优秀成员的团队,如果缺乏沟通,最后可能连设计都出不来。这种例子现实中可以找到很多。
标准和风格
我们总是在不知不觉之中使用各种各样的标准和风格。在团队设计中,我们为了提高决策的效率,可以考虑使用统一的标准和风格。统一的标准和风格并不是一朝一夕形成的。因为每个人都有自己不同的习惯和经历,强制性的要求开发人员使用统一的标准(风格)容易引起开发人员的不满。因此在操作上需要注意技巧。对架构设计而言,比较重要的标准(风格)包括以下的这些类别:
界面设计
流程设计
建模规范
编码规范
持久层设计
测试数据
在我的经验中,有一些组织平时并不注意标准(风格)的积累,认为这种积累属于雕虫小技,但正是这些小技,能够非常有效的提高沟通的效率和降低开发人员的学习曲线。试想一下,如果一个团队中所有人写出的代码都是不同标准和风格的,那么理解起来肯定会困难许多。当然,我们没有必要自己开发一套标准(风格)出来,现实中有很多可以直接借用的资料。最好的标准是UML语言,我们可以从UML的官方网站下载到最新的规范,常用的编码标准更是随处可见。不过虽然有了统一的标准,如果风格不统一,同样会造成沟通的障碍。例如下图显示的类图,虽然它们表示的是同一个类,但是由于版型、可视性、详细程度的差别,看起来又很大的差别。而在其它的标准中,这种差别也是普遍存在的。因此,我们在使用了统一的标准之后,还应该使用同样的风格。Scott W. Ambler专门成立了一个网站讨论UML的建模风格的相关问题,有兴趣的读者可以做额外的阅读。
图 4. 两种风格的类图

在统一的风格的基础上更进一步的是使用术语。使用沟通双方都了解专门的术语,可以代表大量的信息。最好的术语的范例就是设计模式的模式名。如果沟通的双方都了解设计模式,那么一方只需要说这部分的设计可以使用工厂模式,另一方就能够理解,而不用再详细的解释设计的思路。这种的沟通方式是最高效的,但它所需要的学习曲线也会比较陡。
团队设计的四明确
为了最大程度的提高团队设计的高效性,可以从4个方面来考虑:
1、明确目标
泛泛的召开架构讨论会议是没有什么意义的,一个没有鲜明主题的会议也不会有什么结果。在源自需求的模式中,我们谈到说可以有非功能需求的架构,可以有功能需求的架构。因此,在进行团队设计之前,我们首先也需要确定,此次要解决什么问题,是讨论业务逻辑的架构,还是技术架构;是全局性的架构,还是各模块的架构。
2、明确分工
我们之所以重视团队,很重要的额一个原因就是不同的成员有不同的擅长的区域。有些成员可能擅长于业务逻辑的建模,有的擅长于原型设计,有的擅长于数据库设计,有的则擅长于Web编程。你能够想象一个软件没有界面吗?(有些软件可能是这种情况)你能够想象一个软件只有数据库,而没有处理逻辑吗?因此,架构设计就需要综合的考虑各个方面,充分利用成员的优势。这就要求团队的各个成员都能够明确自己的分工。
3、明确责权
除了明确自己的分工,每位成员都需要清楚自己的责任。没有责任,分工就不会有任何的效力。每位成员都需要明确自己要做些什么。当然,和责任相对的,没有成员还需要知道自己的权力是什么。这些清楚了,进行高效的沟通的前提就具备了。每次架构的讨论下来,每个人都清楚,自己要做些什么,自己需要要求其他人做些什么,自己该对谁负责。如果这些问题回答不了,那这次的讨论就白费了。
4、明确沟通方式
这里使用沟通方式可能有一点点不恰当,为了明确的表达意思,大家可以考虑信息流这个词。一个完整架构包括几个方面,分别都由那些人负责,如何产生,产生的整个过程应该是什么样的?这样的一个信息流程,囊括了上面提到的三个明确。如果团队的每一个人都能够为架构的产生而努力,并顺利的设计出架构,那么这样的流程是完美的。如果你发现其中的一些人不知道做些什么,那么,这就是流程出问题的现象了。完美的流程还会有一个额外的副产品,架构产生之后,团队对于软件的设计已经是非常的清晰了。因为我们提倡的是尽可能多的开发人员参与架构的设计。
不仅仅是架构
讨论到这里,其实有很多的内容已经脱离了架构设计了。也就是说,很多的原则和技巧都是可以用于软件开发的其它活动的。至于哪一些活动能够利用这些方法呢?大家可以结合自己的实际情况,来思考这个问题。提示一点,关键的入手处在于目前效率较低之处。

no060-1.jpg (19.79 KB, 下载次数: 73)

no060-1.jpg

论坛徽章:
0
4 [报告]
发表于 2003-08-27 08:33 |只看该作者

敏捷思维

敏捷思维-架构设计中的方法学(5)
简单设计
--------------------------------------------------------------------------------

来自:IBM China 作者:林星 [2003/04/02]
XP非常强调简单的设计原则:能够用数组实现的功能决不用链表。在其它Agile方法中,简单的原则也被反复的强调。在这一章,我们就对简单性做一个全面的了解。
Context
架构应该设计到什么程度?
Problem
软件的架构都是非常的复杂的,带有大量的文档和图表。开发人员花在理解架构本身上的时间甚至超出了实现架构的时间。在前面的文章中,我们提到了一些反对象牙塔式架构的一个原因,而其中的一个原因就是象牙塔式架构的设计者往往在设计时参杂进过多的自身经验,而不是严格的按照需求来进行设计。
在软件开发领域,最为常见的设计就是"Code and Fix"方式的设计,设计随着软件开发过程而增长。或者,我们可以认为这种方式根本就不能算是设计,它抱着一种船到桥头自然直的态度,可是在设计不断改动之后,代码变得臃肿且难以理解,到处充满着重复的代码。这样的情形下,架构的设计也就无从谈起,软件就像是在风雨中的破屋,濒临倒塌。
针对于这种情形,新的设计方式又出现了,Martin Fowler称这种方式为"lanned Design"。和建筑的设计类似,它强调在编码之前进行严格的设计。这也就是我们在团队设计中谈到的架构设计师的典型做法。设计师们通常不会去编程,理由是在土木工程中,你不可能看到一位设计师还要砌砖头。
"lanned Design"较之"Code and Fix"进步了许多,但是还是会存在很多问题。除了在团队设计中我们谈的问题之外,需求变更将会导致更大的麻烦。因此,我们理所当然的想到进行"弹性设计":弹性的设计能够满足需求的变更。而弹性的设计所付出的代价就是复杂的设计。
题外话:
这里我们谈论"lanned Design"引出的一些问题,并没有任何排斥这种方式的意思。"lanned Design"还是有很多可取之处的,但也有很多需要改进的地方。事实上,本文中我们讨论的架构设计方式,本质上也是属于"lanned Design"方式。和"lanned Design"相对应的方式是XP所主张的"Evolutionary Design"方式,但是这种方式还有待于实践的检验,并不能简单的说他就一定要比"lanned Design"先进或落后。但可以肯定的一点是:"Evolutionary Design"方式中有很多的思想和技巧是值得"lanned Design"借鉴的。
Solution
XP中有两个非常响亮的口号:"Do The Simplest Thing that Could Possibly Work"和"You Aren't Going to Need It"(通常称之为YAGNI)。他们的核心思想就是不要为了考虑将来,把目前并不需要的功能加到软件中来。
粗看之下,会有很多开发人员认为这是不切实际的口号。我能理解这种想法,其实,在我热衷于模式、可重用组件技术的时候,我对XP提倡的简单的口号嗤之以鼻。但在实际中,我的一些软件因为复杂设计导致开发成本上升的时候,我重新思考这个问题,发现简单的设计是有道理的。
降低开发的成本
不论是模式,可重用组件,或是框架技术,目的都是为了降低开发的成本。但是他们的方式是先进行大量的投入,然后再节省后续的开发成本。因此,架构设计方面的很多思路都是围绕着这种想法展开的,这可能也是导致开发人员普遍认为架构设计高不可攀的原因。
XP的方式恰恰相反,在处理第一个问题的时候,不必要也不可能就设计出具有弹性、近乎完美的架构来。这项工作应该是随着开发的演进,慢慢成熟起来的。我不敢说这种方式肯定正确,但是如果我们把生物的结构视同为架构,这种方式不是很类似于自然界中生物的进化方式吗?
在一开始就制作出完美的架构的设想并没有错,关键是很难做到这一点。总是会有很多的问题是你在做设计时没有考虑到的。这样,当一开始花费大量精力设计出的"完美无缺"的架构必然会遇到意想不到的问题,这时候,复杂的架构反而会影响到设计的改进,导致开发成本的上升。这就好比如果方向错了,交通工具再快,反而导致错误的快速扩大。Martin Fowler在他的论文中说,"Working on the wrong solution early is even more wasteful than working on the right solution early"(提前做一件错事要比提前做一件对的事更浪费时间),相信也是这个道理。
更有意思的是,通常我们更有可能做错。在我们进行架构设计的时候,我们不可能完全取得详细的需求。事实上,就算你已经取得了完整的需求,也有可能发生变化。这种情况下做出的架构设计是不可能不出错的。这样,浪费大量的时间在初始阶段设计不可能达到的"完美架构",倒不如把时间花在后续的改进上。
提升沟通的效率
我们在团队设计中已经谈过了团队设计的目标之一就是为了降低沟通的成本,以期让所有人都能够理解架构。但是如果架构如果过于复杂,将会重新导致沟通成本的上升,而且,这个成本并不会随着项目进行而降低,反而会因为上面我们提到的遇到新的问题导致沟通成本的持续上升。
简单的架构设计可以加快开发团队理解架构的速度。我们可以通过两种方式来理解简单的含义。首先,简单意味着问题的解不会非常的复杂,架构是解决需求的关键,无论需求再怎么复杂多变,总是可以找出简单稳定的部分,我们可以把这个简单稳定的部分做为基础,再根据需要进行改进扩展,以解决复杂的问题。在示例中,我们提到了measurement pattern,它就是按照这种想法来进行设计的。
其次,简单性还体现在表示的简单上。一份5页的文档就能够表达清楚的架构设计为什么要花费50页呢?同样的道理,能够用一副简单的图形就能够表示的架构设计也没有必要使用文档。毕竟,面对面的沟通才是最有效率的沟通,文档不论如何的复杂,都不能被完全理解,而且,复杂的文档,维护起来也需要花费大量的时间。只有在两种情况下,我们提倡使用复杂的文档:一是开发团队没有办法做到面对面沟通;二是开发成果要作为团队的知识积累起来,为下一次开发所用。
考虑未来
我们之所以考虑未来,主要的原因就是需求的不稳定。因此,我们如果考虑未来可能发生的需求变化,就会不知觉的在架构设计中增加复杂的成分。这违背的简单的精神。但是,如果你不考虑可能出现的情况,那些和目前设计格格不入的改变,将会导致大量的返工。
还记得YAGNI吗?原则上,我们仍然坚持不要在现有的系统中为将来可能的情况进行设计。但是,我们必须思考,必须要为将来可能出现的情况做一些准备。其实,软件中了不起的接口的思想,不就是源于此吗?因此,思考未来,但等到需要时再实现。
变更案例有助于我们思考未来,变更案例就是你在将来可能要(或可能不要)满足的,但现在不需要满足的需求。当我们在做架构设计的时候,变更案例也将会成为设计的考虑因素之一,但它不可能成为进行决策的唯一考虑因素。很多的时候,我们沉迷于设计通用系统给我们带来的挑战之中,其实,我们所做的工作对用户而言是毫无意义的。
架构的稳定
架构简单化和架构的稳定性有什么关系吗?我们说,架构越简单,其稳定性就越好。理由很简单,1个拥有4个方法和3个属性的类,和1个拥有20个方法和30属性的类相比,哪一个更稳定?当然是前者。而架构最终都是要映射到代码级别上的,因此架构的简单将会带来架构的稳定。尽可能的让你的类小一些,尽可能的让你的方法短一些,尽可能的让类之间的关系少一些。这并不是我的忠告,很多的设计类的文章都是这么说的。在这个话题上,我们可以进一步的阅读同类的文章(关于 refactoring 的思考)。
辨正的简单
因此,对我们来说,简单的意义就是不要把未来的、或不需要实现的功能加入到目前的软件中,相应的架构设计也不需要考虑这些额外的需求,只要刚好能够满足当前的需求就好了。这就是简单的定义。可是在现实之中,总是有这样或者那样的原因,使得设计趋向复杂。一般来说,如果一个设计对团队而言是有价值的,那么,付出一定的成本来研究、验证、发展、文档化这个设计是有意义的。反之,如果一个设计没有很大的价值或是发展它的成本超过了其能够提供的价值,那就不需要去考虑这个设计。
价值对不同的团队来说具有不同的含义。有时候可能是时间,有时候可能是用户价值,有时候可能是为了团队的设计积累和代码重用,有时候是为了获得经验,有时候是为了研究出可重用的框架(FrameWork)。这些也可以称为目的,因此,你在设计架构时,请注意先确定好你的目的,对实现目的有帮助的事情才考虑。
Scott W.Ambler在他的文章中提到一个他亲身经历的故事,在软件开发的架构设计过程中,花了很多的时间来设计数据库到业务逻辑的映射架构,虽然这是一件任何开发人员都乐意专研的事情(因为它很酷)。但他不得不承认,对用户来说,这种设计先进的架构是没有太大的意义的,因为用户并不关心具体的技术。当看到这个故事的时候,我的触动很大。一个开发人员总是热衷于新奇的技术,但是如果这个新奇技术的成本由用户来承担,是不是合理呢?虽然新技术的采用能够为用户带来效益,但是没有人计算过效益背后的成本。就我开发过的项目而言,这个成本往往是大于效益的。这个问题可能并没有确定的答案,只能是见仁见智了。
例1.Java的IO系统
从Java的IO系统设计中,我们可以感受到简单设计的困难。
IO系统设计的困难性向来是公认的。Java的IO设计的一个目的就是使IO的使用简单化。在Java的1.0中,Java的IO系统主要是把IO系统分为输入输出两个大部分,并分别定义了抽象类InputStream和OutputStream。从这两个的抽象类出发,实现了一系列不同功能的输入输出类,同时,Java的IO系统还在输入输出中实现了FilterInputStream和FilterOutputStream的抽象类以及相关的一系列实现,从而把不同的功能的输入输出函数连接在一起,实现复杂的功能。这个实现其实是Decorator模式(由于没有看过源码和相关的资料,这里仅仅是根据功能和使用技巧推测,如果大家有不同的意见,欢迎来信讨论)。
因此,我们可以把多个对象叠加在一起,提供复杂的功能:
DataInpuStream in =
new DataInputStream(
new BufferedInputStream(
new FileInputStream("test.txt";

上面的代码使用了两个FilterInputStream:DataInpuStream和BufferedInputStream,以实现读数据和缓冲的功能,同时使用了一个InputStream:FileInputStream,从文件中读取流数据。虽然使用起来不是很方便,但是应该还是非常清晰的设计。
令设计混乱的是既不属于InputStream,也不属于OutputStream的类,例如RandomAccessFile,这正表明,由于功能的复杂化,使得原先基于输入输出分类的设计变得混乱,根据我们的经验,我们说设计需要Refactoring了。因此,在Java1.1中,IO系统被重新设计,采用了Reader和Writer位基础的设计,并增加了新的特性。但是目前的设计似乎更加混乱了,因为我们需要同时使用1.0和1.1两种不同的IO设计。


简单并不等于实现简单
说到这里,如果大家有一个误解,认为一个简单的架构也一定是容易设计的,那就错了。简单的架构并不等于实现起来也简单。简单的架构需要设计者花费大量的心血,也要求设计者对技术有很深的造诣。在我们正在进行的一个项目中,一开始设计的基础架构在实现中被修改了几次,但每修改一次,代码量都减少一分,代码的可读性也就增强一分。从心理的角度上来说,对自己的架构进行不断的修改,确实是需要一定的勇气的。因为不论是设计还是代码,都是开发人员的心血。但跨出这一步是值得的。
右侧的例子讨论了Java的IO设计,Java类库的设计应该来说是非常优秀的,但是仍然避免不了重新的修改。实际上,在软件开发领域,由于原先的设计失误而导致后来设计过于复杂的情况比比皆是(例如微软的OLE)。同样的,我们在设计软件的时候,也需要对设计进行不断的修改。能够实现复杂功能,同时自身又简单的设计并不是一件容易的事情。
简单设计需要什么样的设计师
简单的架构需要全面的设计师。什么才是全面的设计师,我的定义是既能够设计,又能够编码。我们在团队设计模式中就已经谈过象牙塔式架构和象牙塔式架构设计师。他们最容易犯的一个毛病就是设计和代码的脱离。从我们自己的经验来看,即使在设计阶段考虑的非常完美的架构,在编码阶段也会出现这样或那样的问题。从而导致架构实现变得复杂。最明显的特征就是在编码时出现了有大量方法的类,或是方法很长的类。这表明架构和代码脱钩了。在我们的开发过程中,不只一次出现这种现象,或者说,出现了坏味道(Bad Smell)。Refactoring的技巧也同样有助于识别坏味道。
一次的架构设计完成后,开发人员可以按照设计,快速的编程。可在一段时间之后,新的特色不断的加入,我们发现代码开始混乱,代码量增大,可读性下降,调试变得困难,代码不可控制的征兆开始出现。我们就知道,架构的设计需要调整了。这属于我们在后面所提到的Refactoring模式。而我们在这里要说的是,如果架构的设计师不参与编码,它是无法感受到坏味道的,因此也就不会主动的对设计进行改进。要解决这个问题,最好的办法是让设计师参与代码的编写,尤其是重要架构的现实部分需要设计师的参与。如果设计师没有办法参与编码,那就需要一种机制,能够把代码反馈给设计师,让他在适当的时候,重新考虑改进架构。一个可能的办法是Code Review。让设计师审核代码,以确保编码者真正了解了架构设计的意图。
例1.Java的IO系统
从Java的IO系统设计中,我们可以感受到简单设计的困难。
IO系统设计的困难性向来是公认的。Java的IO设计的一个目的就是使IO的使用简单化。在Java的1.0中,Java的IO系统主要是把IO系统分为输入输出两个大部分,并分别定义了抽象类InputStream和OutputStream。从这两个的抽象类出发,实现了一系列不同功能的输入输出类,同时,Java的IO系统还在输入输出中实现了FilterInputStream和FilterOutputStream的抽象类以及相关的一系列实现,从而把不同的功能的输入输出函数连接在一起,实现复杂的功能。这个实现其实是Decorator模式(由于没有看过源码和相关的资料,这里仅仅是根据功能和使用技巧推测,如果大家有不同的意见,欢迎来信讨论)。
因此,我们可以把多个对象叠加在一起,提供复杂的功能:
DataInpuStream in =
new DataInputStream(
new BufferedInputStream(
new FileInputStream("test.txt";
上面的代码使用了两个FilterInputStream:DataInpuStream和BufferedInputStream,以实现读数据和缓冲的功能,同时使用了一个InputStream:FileInputStream,从文件中读取流数据。虽然使用起来不是很方便,但是应该还是非常清晰的设计。
令设计混乱的是既不属于InputStream,也不属于OutputStream的类,例如RandomAccessFile,这正表明,由于功能的复杂化,使得原先基于输入输出分类的设计变得混乱,根据我们的经验,我们说设计需要Refactoring了。因此,在Java1.1中,IO系统被重新设计,采用了Reader和Writer位基础的设计,并增加了新的特性。但是目前的设计似乎更加混乱了,因为我们需要同时使用1.0和1.1两种不同的IO设计。
例2. measurement pattern
在分析模式一书中有一个measurement pattern(测量模式),原来它是为了要解决现实中各种各样纷繁复杂的可测量的属性。例如,一个医疗系统中,可能会有身高多高,体重多种,血压多少等上千种可测量的属性。如果分别表示它们,必然导致系统复杂性的上升。因此measurement pattern就从这些属性的可测量的共性出发,研究新的解决方法,提出了measurement pattern的想法:
如图所示,把可测量的属性(Measurement)做为Phenomenon Type的实例,此外,每一个的Person可以拥有多个的Measurement,同时,Measurement还对应处理的属性,例如图中的Quantity,就表示了Measurement的数量和单位。比如,一个人的体重是65公斤,那么,Phenomenon Type就是体重,Quantity的amount是65,units是公斤。

图 5.牋 measurement pattern 的类图
这其实是一个很简单的设计,但它清楚的表示了属性之间的关系,简化了数千种的属性带来的复杂性。此外,我们进一步思考,就会发现,这种架构只是针对目前出现属性众多的问题的基本解决方法,它还可以根据具体的需要进行扩展,例如,实现动态添加单位,或实现不同单位的转化等问题。
因此,我们这里展示的其实是一种思考的方法,假想一下,当你在面对一个复杂的医疗系统时,大量的属性和不同的处理方式,你是不是可以从这样复杂的需求中找出简单的部分来呢?在我们架构设计的第一篇中,我们谈到架构设计的本质在于抽象,这里例子就是最典型的一个例子,在我们传统的想法中,我们都会把身高、体重等概念做为属性或是类,但是为了满足这里的需求,我们对这些具体的概念做一个抽象,提出可测量类别的概念,并把它设计为类(Phenomenon Type),而把具体的概念做为实例。这种抽象的思想在软件设计中无处不在,例如元类的概念。
更深入的理解
下一章中我们将会讨论迭代设计,其中还会涉及到简单设计的相关知识。建议可以将两章的内容结合起来看。

no061-1.jpg (6.75 KB, 下载次数: 81)

no061-1.jpg

论坛徽章:
0
5 [报告]
发表于 2003-08-27 08:38 |只看该作者

敏捷思维

敏捷思维-架构设计中的方法学(6)
迭代设计
--------------------------------------------------------------------------------

来自:IBM China 作者:林星 [2003/04/02]
迭代是一种软件开发的生命周期模型,在设计中应用迭代设计,我们可以得到很多的好处。
Context
在软件生命周期中,我们如何对待架构设计的发展?
Problem
架构设计往往发生在细节需求尚未完成的时候进行的。因此,随着项目的进行,需求还可能细化,可能变更。原先的架构肯定会有不足或错误的地方。那么,我们应该如何对待原先的设计呢?
我们在简单设计模式中简单提到了"lanned Design"和"Evolutionary Design"的区别。XP社团的人们推崇使用"Evolutionary Design"的方式,在外人看来,似乎拥护者们从来不需要架构的设计,他们采用的方式是一开始就进入代码的编写,然后用Refactoring来改进代码的质量,解决未经设计导致的代码质量低下的功能。
从一定程度上来说,这个观点并没有错,它强调了代码对软件的重要性,并通过一些技巧(如Refactoring)来解决缺乏设计的问题。但我并不认同"Evolutionary Design"的方式,在我看来,一定程度上的"lanned Design"是必须的,至少在中国的软件行业中,"lanned Design"还没有成为主要的设计方向。借用一句明言,"凡事预则立,不预则废",在软件设计初期,投入精力进行架构的设计是很有必要的,这个架构是你在后续的设计、编码过程中依赖的基础。但是,一开始我们提到的设计改进的问题依然存在,我们如何解决它呢?
在简单设计模式中,我们提到了设计改进的必要性,但是,如果没有一种方法去控制设计的改进的话,那么设计改进本身就是一场噩梦。因此,何时改进,怎么改进, 如何控制,这都是我们需要面对的问题。
Solution
为了实现不断的改进,我们将在开发流程中引入迭代的概念。迭代的概念在我的另一篇文章--《需求的实践》中已经提到,这里我们假设读者已经有了基本的迭代的概念。
软件编码之前的工作大致可以分为这样一个工作流程:

上图中的流程隐含着一个信息的损失的过程。来自于用户的需求经过整理之后,开发人员就会从中去掉一些信息,同样的事情发生在后面的过程中,信息丢失或变形的情况不断的发生。这里发生了什么问题?应该说,需求信息的失真是非常普遍的,我们缺少的是一种有效的办法来抑止失真,换句话说,就是缺少反馈。
如果把眼睛蒙上,那我们肯定没有办法走出一条很长的直线。我们走路的时候都是针对目标不断的调整自己的方向的。同样的,漫长的软件开发过程如果没有一种反馈机制来调整方向,那最后的软件真是难以想象。 所以我们引入了迭代周期。
初始设计和迭代设计
在团队设计中,我们一直在强调,设计组最开始得到的设计一定只是一个原始架构,然后把这个原始架构传播到每一位开发者的手中,从而在开发团队中形成共同的愿景。(愿景(Vision):源自于管理学,表示未来的愿望和景象。这里借用来表示软件在开发人员心中的样子。在后面的文章中我们会有一个章节专门的讨论架构愿景。)
迭代(Iterate)设计,或者我们称之为增量(Incremental)设计的思想和XP提倡的Evolutionary Design有异曲同工之妙。我们可以从XP、Crystal、RUP、ClearRoom等方法学中对比、体会迭代设计的精妙之处:每一次的迭代都是在上一次迭代的基础上进行的,迭代将致力于重用、修改、增强目前的架构,以使架构越来越强壮。在软件生命周期的最后,我们除了得到软件,还得到了一个非常稳定的架构。对于一个软件组织来说,这个架构很有可能就是下一个软件的投入或参考。
我们可以把早期的原始架构当作第一次迭代前的早期投入,也可以把它做为第一次迭代的重点,这些都是无所谓的。关键在于,原始架构对于后续的架构设计而言是非常重要的,我们讨论过架构是来源于需求的,但是原始架构应该来源于那些比较稳定的需求。
TIP:现实中迭代设计退化为"Code and Fix"的设计的情况屡见不鲜("Code and Fix"参见简单设计)。从表面上看,两者的做法并没有太大的差别,都是针对原有的设计进行改进。但是,二者效果的差别是明显的:"Code and Fix"是混沌的,毫无方向感可言,每一次的改进只是给原先就已摇摇欲坠的积木上再加一块积木而已。而迭代设计的每一次改进都朝着一个稳定的目标在前进,他给开发人员带来信心,而不是打击。在过程上,我们说迭代设计是在控制之下的。
从实践的经验中,我们发现,把原该在目前就该解决的问题退后是造成这一问题的主要原因之一。因此,请严格的对待每一次的迭代,确保计划已经完成、确保软件的质量、确保用户的需求得到满足,这样才是正统的迭代之路。

单次的迭代
我们说,每一次的迭代其实是一个完整的小过程。也就是说,它同样要经历文章中讨论的这些过程模式。只不过,这些模式的工作量都不大,你甚至可以在很短的时间内做完所有的事情。因此,我们好像又回到了文章的开头,重新讨论架构设计的过程。
单次迭代最令我们兴奋的就是我们总是可以得到一个在当前迭代中相当稳定的结果,而不像普通的架构设计那样,我们深怕架构会出现问题,但又不得不依赖这个架构。从我们的心理上来分析,我们是在持续的建设架构中,我们不需要回避需求的变更,因为我们相信,在需求相对应的迭代中,我们会继续对架构进行改进。大家不要认为这种心理的改变是无关紧要的,我起初并没有意识到这个问题,但是我很快发现新的架构设计过程仍然笼罩在原先的惧怕改变的阴影之下的时候,迭代设计很容易就退化为"Code and Fix"的情形。开发人员难以接受新方法的主要原因还是在心理上。因此,我不得不花了很多的时间来和开发人员进行沟通,这就是我现实的经验。
迭代的交错
基于我们对运筹学的一点经验,迭代设计之间肯定不是线性的关系。这样说的一个原因架构设计和后续的工作间还是时间差的。因此,我们不会傻到把时间浪费在等待其它工作上。一般而言,当下一次迭代的需求开始之后,详细需求开始之前,我们就已经可以开始下一次迭代的架构设计了。
各次迭代之间的时间距离要视项目的具体情况而定。比如,人员比较紧张的项目中,主要的架构设计人员可能也要担任编码人员的角色,下一次迭代的架构设计就可能要等到编码工作的高峰期过了之后。可是,多次的交错迭代就可能产生版本的问题。比如,本次的迭代的编码中发现了架构的一个问题,反馈给架构设计组,但是架构设计组已经根据伪修改的本次迭代的架构开始了下一次迭代的架构设计,这时候就会出现不同的设计之间的冲突问题。这种情况当然可以通过加强对设计模型的管理和引入版本控制机制来解决,但肯定会随之带来管理成本上升的问题,而这是不符合敏捷的思想的。这时候,团队设计就体现了他的威力了,这也是我们在团队设计中没有提到的一个原因。团队设计通过完全的沟通,可以解决架构设计中存在冲突的问题。
迭代频率
XP提倡迭代周期越短越好(XP建议为一到两周),这是个不错的提议。在这么短的一个迭代周期内,我们花在架构设计上的时间可能就只有一两个小时到半天的时间。这时候,会有一个很有意思的现象,你很难去区分架构设计和设计的概念了。因为在这么短的一个周期之内,完成的需求数量是很少的,可能就只有一两个用例或用户素材。因此,这几项需求的设计是不是属于架构设计呢?如果是的话,由于开发过程是由多次的迭代组成的,那么开发过程中的设计不都属于架构设计了吗?我们说,架构是一个相对的概念,是针对范围而言的,在传统的瀑布模型中,我们可以很容易的区分出架构设计和普通设计,如果我们把一次迭代看作是一个单独的生命周期,那么,普通的设计在这样一个范围之内也就是架构设计,他们并没有什么两样。但是,迭代周期中的架构设计是要遵循一定的原则的,这我们在下面还会提到。
我们希望迭代频率越快越好,但是这还要根据现实的情况而定。比如数据仓库项目,在项目的初期阶段,我们不得不花费大量的时间来进行数据建模的工作,这其实也是一项专门针对数据的架构设计,建立元数据,制定维,整理数据,这样子的过程很难分为多次的迭代周期来实现。
如何确定软件的迭代周期
可以说,如果一支开发团队没有相关迭代的概念,那么这支团队要立刻实现时隔两周迭代周期是非常困难的,,同时也是毫无意义的。就像我们在上面讨论的,影响迭代周期的因素很多,以至于我们那无法对迭代周期进行量化的定义。因此我们只能从定性的角度分析迭代周期的发展。
另一个了解迭代的方法是阅读XP的相关资料,我认为XP中关于迭代周期的使用是很不错的一种方法,只是他强调的如此短的迭代周期对于很多的软件团队而言都是难以实现的。
迭代周期的引入一定是一个从粗糙到精确的过程。迭代的本质其实是短周期的计划,因此这也是迭代周期越短对我们越有好处的一大原因,因为时间缩短了,计划的可预测性就增强了。我们知道,计划的制定是依赖于已往的经验,如果原先我们没有制定计划或细节计划的经验,那么我们的计划就一定是非常粗糙,最后的误差也一定很大。但是这没有关系,每一次的计划都会对下一次的计划产生正面的影响,等到经验足够的时候,计划将会非常的精确,最后的误差也会很小。
迭代周期的确定需要依赖于单位工作量。单位工作量指的是一定时间内你可以量化的最小的绩效。最简单的单位工作量是每位程序员一天的编码行数。可惜显示往往比较残酷,团队中不但有程序员的角色,还有设计师、测试人员、文档制作人员等角色的存在,单纯的编码行数是不能够作为唯一的统计依据的。同样,只强调编码行数,也会导致其它的问题,例如代码质量。为了保证统计的合理性,比较好的做法是一个团队实现某个功能所花费的天数作为单位工作量。这里讨论的内容实际是软件测量技术,如果有机会的话,再和大家探讨这个问题。
迭代周期和软件架构的改进
我们应用迭代方法的最大的目的就是为了稳步的改进软件架构。因此,我们需要了解架构是如何在软件开发的过程中不断演进的。在后面的文章中,我们会谈到用Refactoring的方法来改进软件架构,但是Refactoring的定义中强调,Refactoring必须在不修改代码的外部功能的情况下进行。对于架构来说,我们可以近乎等价的认为就是在外部接口不变的情况下对架构进行改进。而在实际的开发中,除非非常有经验,否则在软件开发全过程中保持所有的软件接口不变是一件非常困难的事情。因此,我们这里谈的架构的改进虽然和Refactoring有类似之处,但还是有区别的。
软件架构的改进在软件开发过程会经历一个振荡期,这个振荡期可能横跨了数个迭代周期,其间架构的设计将会经历剧烈的变化,但最后一定会取向于平稳。(如果项目后期没有出现设计平稳化的情况,那么很不幸,你的项目注定要失败了,要么是时间的问题,要么就是需求的问题)。关键的问题在于,我们有没有勇气,在架构需要改变的时候就毅然做出变化,而不是眼睁睁的看着问题变得越来越严重。最后的例子中,我们讨论三个迭代周期,假设我们在第二个周期的时候拒绝对架构进行改变,那么第三个周期一定是有如噩梦一般。变化,才有可能成功。
我们知道变化的重要性,但没有办法知道变化的确切时间。不过我们可以从开发过程中嗅到架构需要变化的气味:当程序中重复的代码逐渐变多的时候,当某些类变得格外的臃肿的时候,当编码人员的编码速度开始下降的时候,当需求出现大量的变动的时候。
例子:
从这一周开始,我和我的小组将要负责对软件项目中的表示层的设计。在这个迭代周期中,我们的任务是要为客户端提供6到10个的视图。由于视图并不很多,表示层的架构设计非常的简单:     

准确的说,这里谈不上设计,只是简单让客户端访问不同的视图而已。当然,在设计的示意图中,我们并没有必要画出所有的视图来,只要能够表达客户端和视图的关联性就可以了。
(架构设计需要和具体的实现绑定,但是在这个例子中,为了着重体现设计的演进,因此把不必要的信息都删掉。在实际的设计中,视图可能是JSP页面,也可能是一个窗口。)
第一个迭代周的任务很快的完成了,小组负责的表示层模块也很顺利的和其它小组完成了对接,一个简陋但能够运转的小系统顺利的发布。客户观看了这个系统的演示,对系统提出了修改和补充。
第二个迭代周中,模块要处理的视图增加到了30个,视图之间存在相同的部分,并且,负责数据层的小组对我们说,由于客户需求的改进,同一个视图中将会出现不同的数据源。由于我们的视图中直接使用了数据层小组提供给我们的数据源的函数,这意味着我们的设计需要进行较大的调整。
考虑到系统的视图的量大大的增加,我们有必要对视图进行集中的管理。前端控制器(Front Control)模式将会是一个不错的技巧。对于视图之间的普遍的重复部分,可以将视图划分为不同的子视图,再把子视图组合为各种各样的视图。这样我们就可以使用组合(Composite)模式:

no062-2.jpg (6.57 KB, 下载次数: 75)

表示层的架构设计

表示层的架构设计

no062-1.jpg (5.89 KB, 下载次数: 77)

软件编码之前的工作大致工作流程

软件编码之前的工作大致工作流程

论坛徽章:
0
6 [报告]
发表于 2003-08-27 08:41 |只看该作者

敏捷思维

客户的请求集中提交给控制器,控制器接受到客户的请求之后,根据一定的规则,来提供不同的视图来反馈给客户。控制器是一个具有扩展能力的设计,目前的视图数量并不多,因此仍然可以使用控制器来直接分配视图。如果视图的处理规则比较复杂,我们还可以使用创建工厂(Create Factory)模式来专门处理生成视图的问题。对于视图来说,使用组合模式,把多个不同数据源的视图组合为复杂的视图。例如,一个JSP的页面中,可能需要分为头页面和尾页面。
项目进入第三个迭代周期之后,表示层的需求进一步复杂化。我们需要处理权限信息、需要处理数据的合法性判断、还需要面对更多的视图带来的复杂程度上升的问题。
表示层的权限处理比较简单,我们可以从前端控制器中增加权限控制的模块。同时,为了解决合法性判断问题,我们又增加了一个数据过滤链模块,来完成数据的合法性判断和转换的工作。为了不使得控制器部分的功能过于复杂,我们把原先属于控制器的视图分发功能转移到新的分发器模块,而控制器专门负责用户请求、视图的控制。

图a     

我们来回顾这个例子,从迭代周期1中的需求最为简单,其实,现实中的项目刚开始的需求虽然未必会像例子中的那么简单,但一定不会过于复杂,因此迭代周期1的设计也非常的简单。到了迭代周期2的时候,需求开始变得复杂,按照原先的架构继续设计的话,必然会导致很多的问题,因此对架构进行改进是必要的。我们看到,新的设计能够满足新的需求。同样的,迭代周期3的需求更加的复杂,因此设计也随之演进。这就是我们在文章的开始提到的"Evolutionary Design"的演进的思想。

no062-5.jpg (12.59 KB, 下载次数: 77)

图a

图a

no062-3.jpg (5 KB, 下载次数: 77)

组合(Composite)模式:

组合(Composite)模式:

论坛徽章:
0
7 [报告]
发表于 2003-08-27 08:45 |只看该作者

敏捷思维

敏捷思维-架构设计中的方法学(
架构愿景
--------------------------------------------------------------------------------

来自:IBM China 作者:林星 [2003/04/17]
从这一篇开始,我们将会进入另一个不同的主题,和前面所讨论的模式专注于组织、过程、方法不同,以后介绍的模式更偏重于设计。但是过程、方法的影子依然在我们的讨论中隐约可见。
架构愿景是一个很简单的模式,在软件开发中所占的时间也很短。但是这并不意味着架构愿景不重要。相反,它会是设计过程不可或缺的一环。
Context
在单次的迭代开始阶段,我们已经收集好了单次迭代的需求。
Problem
架构和分析设计是密不可分的,有时候很难说得清楚架构的定义,但架构应该能够描述软件的整体。架构包括了软件的各个方面,但是每一个设计细节总是需要单独考虑,这时候就会出现设计细节之间、以及设计细节和架构之间的不一致。
架构设计的各个部分之间的设计冲突是很容易发生的。发生的概率及频率和团队的规模成正比、和沟通的频度及效果成反比。在很多次的项目开发过程中,我们发现了多处的相同功能的代码,原因是代码的作者并不知道别人已经实现了这个功能了。这可能只是浪费了一点精力,可如果不同模块间的设计冲突导致了软件无法正常运行的时候,我们就需要坐下来好好的审视,究竟发生了什么。
Solution
我们需要建立一个架构愿景。架构愿景应该能够提供软件全局视图,包括所有的重要部分,定义了各个部分的责任和之间的关系,而且还定义了软件设计需要满足的原则。而这个架构愿景的设计,应该是满足源自需求模式的,也就是说,部分的划分和部分的设计,都是根据需求而进行的。同时,架构愿景应该要能够满足架构的其它各种特点,例如简单、可扩展性、抽象性。简单来说,我们把架构愿景当作是一个mini的架构设计。
由于我们是在单次的迭代中讨论架构愿景,因此从整体上考虑,架构愿景是也是在不断的变化的。这是很自然的,因为架构愿景代表了架构的设计,架构愿景的演进代表了架构设计的演进。
架构的愿景是相对于一个范围来说的,在一个特定的软件功能范围之内,谈架构愿景才有实际的意义,例如针对软件的全局或某个子模块。在这个特定的范围中,订立了架构愿景之后,这个范围内的所有设计原则将不能违背架构愿景。这是非常重要的,是架构愿景的最大的用处。有了这样的保证,我们就可以保证设计的一致性和有效性。任何一项设计的加入,都能够融入到原先的架构中,使得软件更加的完善,而不是更加的危险。
当然,要做到这一点并不是一件容易的事情。特别需要指出的是,架构愿景模式仅仅是实现该目的的一条道路,并不是一个充分条件。如果在设计中愿景不能够贯彻其意志,或是愿景制定本身就存在问题,那么要想达到上述的效果,几乎是不可能的事情。此外,该模式仅仅只是达到该目的的第一步,我们在接下去的模式中会发现还需要很多方面的配合。
架构愿景的层次
我们根据架构适用范围的不同,把架构愿景分为几个类别讨论:
软件全局
软件全局的架构是我们所最关心的,因此也会花费最多的笔墨。
软件全局中的架构愿景一般很难具体化到代码级别,其实你会发现,就算是具体化到了代码级别,也会因为实际中存在的问题,导致代码没有太多的价值。因此,为软件全局设置的架构愿景可以以原则、或是模式名的方式体现,并用自然语言或伪代码描述。例如,可以为一个系统规定三层架构作为其愿景,并指出三层的分类原则。注意,我们需要指出分类原则,否则规定三层架构并没有太大的意义,因为三层架构随着实现平台的不同、开发人员的不同而有很大的差异,如果不能够规定一个可操作的规范,那么愿景是没有意义的。在Java环境下,我们可以这样说:
"客户端采用前端浏览器界面,业务逻辑采用servlet,配合JSP编写,浏览器到服务器的数据采用集中处理,具体的方法是在业务逻辑和前端浏览器之间采用Front Control模式,接受前端浏览器传送过来的数据,并指派给相应的业务逻辑处理。数据的合法性检验分为两个部分:和业务逻辑无关的基本合法性验证在前端使用Java Script处理,和业务逻辑相关的合法性验证在业务逻辑层处理,可以使用一个集中的servlet专门处理错误情况。"
而在Windows环境下,我们知道三层结构的分界和Java中的并不相同(关于这一点,下一篇的文章中将会有详细的描述),我们可以在业务逻辑层或显示层直接操纵数据,非常的方便,因此,在Windows中的架构愿景的描述又不一样。另外,在非面向对象的环境中,在分布式的环境中,在Lotus Notes的环境中,其架构的描述都不一样。
注意到,架构愿景的制定是根据不同的应用环境而变化的,这一点我们在文章的一开始就强调过,这里又出现了相同的问题。我们可以通过一个简单的例子来加深对这个问题的理解。在Java的环境下,特别是J2EE的环境下面,经典的设计思路是把数据库的一张表视作一个类,表中的每一行都是这个类的一个具体的实例,表的每一个字段对应了类的一个变量,类一般支持Find方法,来获取数据不同的实例。这是一种很自然的设计思路,而且可以通过CMP等技术来简化设计的繁琐步骤。可是,在Windows环境下,相信没有多少人会这么做。常用的处理方法是使用记录集(RecordSet)的方式,也就是说针对一组的记录来进行操作,而不是单个的记录。这些记录可以是跨表的(使用SQL的连接),也可以是单表的。由于Windows环境下大多数的编程语言都能够支持记录集方式,因此编写基于记录集方式的程序是相当简单的。当然,我们也可以采用J2EE平台的编程方式,但是会导致编程量的激增,同时也难以达到预计的效果。这种平台的差别导致了架构愿景的差别。
此外,我们注意到我们对架构的描述其实还是不够仔细,这是非常正常的,因为随着开发的深入,我们会不断的完善架构的描述。例如,我们可以写出Front Controll的类框架,写出主要的几个Servlet,以及他们之间的关系。这个时候,使用UML类图会是一个不错的选择,实际上,我非常推崇在架构设计中大量的使用类图。不过在实际的运作中,每个软件团队大多数都有自己熟悉的架构设计思路,使用何种工具并不是主要的问题。
我们从源自需求模式中,学习到架构的设计是来自于需求的,而应用于软件全局的架构则来自于最重要的需求。还记得我们在那个模式中提到的网上宠物店的例子吗?系统采用了MVC模式,sun的官方文档一开始说明了为什么采用MVC模式,MVC模式解决了什么问题,然后开始分析MVC模式的几个组成部分:Model、View、和Controll。其实,MVC中的每一个部分,在真正的代码中,大都代表了一个子系统,但是在目前,我们就非常的清楚系统大致上会是一个什么样子,虽然这时候它还十分的朦胧。
不要视图在全局的架构愿景中就制定出非常细致的规划,更不要视图生成大量的实际代码。因为,你的架构愿景还没有稳定(我们在其后的稳定化的模式中将会讨论稳定的问题),还没有获得大家的同意,也没有经过证明。因此,从整个的开发周期来看,全局架构愿景是随着迭代周期的进行不断发展、修改、完善的。
我们如何确定全局架构愿景工作的完成?一般来说,你的架构设计团队取得了一致的意见就可以结束了,如果问题域是团队所熟悉的,一两个小时就能够解决问题。接下来设计团队把架构愿景传播到整个的开发团队,大家形成一致的认识,不同的意见将会被反馈会来,并在本次的迭代周期(如果时间比较紧迫)或下一次的迭代周期中(如果时间比较宽松)考虑。
子模块级、或是子问题级的架构愿景
这时候的架构愿景已经是比较明确的了,因为已经存在明确的问题域。例如界面的设计、领域模型的设计、持久层的设计等。这里的愿景制定本质上和全局的愿景制定差不多,具体的例子我们也不再举了。但是要注意一点,你不能够和全局愿景所违背。在操作上,全局愿景是设计团队共同制定出来的,而子模块级的架构愿景就可以分给设计子团队来负责,而其审核则还是要设计团队的共同参与。这有两个好处,一是确保各个子模块(子问题)间不至于相互冲突或出现空白地带,二是每个子设计团队可以从别人那里吸取设计经验。
在设计时,同样我们可以参考其它的资料,例如相关的模式、或规范(界面设计指南)。在一个有开发经验的团队,一般都会有开发技术的积累,这些也是可供参考的重要资料。
我们在这个层次的愿景中主要谈一谈子模块(子问题)间的耦合问题。一般来说,各个子模块间的耦合程度相对较小,例如一个MIS系统中,采购和销售模块的耦合度就比较小,而子问题间的耦合程度就比较大,例如权限设计、财务,这些功能将会被每个模块使用。那么,我们就需要为子模块(子问题)制定出合同接口(Contact Interface)。合同的意思就是说这个接口是正式的,不能够随意的修改,因为这个结构将会被其它的设计团队使用,如果修改,将会对其它的团队产生无法预计的影响。合同接口的制定、修改都需要设计团队的通过。此外,系统中的一些全局性的子问题最好是提到全局愿景中考虑,例如在源自需求模式中提到的信贷帐务的例子中,我们就把一个利息计算方式的子问题提到了全局愿景中。
代码级的愿景
严格的说这一层次的愿景已经不是真正的愿景,而是具体设计了。但是我们为了保证对架构设计理解的完整性,还是简单的讨论一下。这一个层次的愿景一般可以使用类图、接口来表示。但在类图中,你不需要标记出具体的属性、操作,你只需要规定出类的职责以及类之间的相互关系就可以了。该层次愿景的审核需要设计子团队的通过。
而设计细分到这个粒度上,执行愿景设计的开发人员可能就只有一两个左右。但是比较重要的工作在于问题如何分解和如何归并。分解主要是从两个维度来考虑,一个是问题大小维,一个是时间长短维。也就是说,你(设计子团队负责人)需要把问题按大小和解决时间的长短分解为更细的子问题,交给不同的开发人员。然后再把开发人员提出的解决方法组合起来。
架构愿景的形成过程
架构愿景的形成的源头是需求,需要特别指出的是,这里的需求主要是那些针对系统基本面的需求。比如说,系统的特点是一个交互式系统,还是一个分布式系统。这些需求将会影响到架构愿景的设计。在收集影响架构愿景的各项需求之后,按照需求的重要性来设计架构愿景。
架构愿景的设计并不需要很复杂的过程,也不需要花费很多的时间。我们已经提过,架构远景的主要目的就是为了能够在开发团队中传播设计思路,因此,架构愿景包括基本的设计思路和基本的设计原则。
值得注意的是,架构远景可能会有多种的视角,下文讨论了一种设计模式的视角。但是实际设计中还可能会基于数据库来设计架构愿景。但在企业信息系统的设计中,我推荐使用领域类的设计,也就是下文中讨论的例子。
架构愿景设计好之后,问题的焦点就转到如何传播架构愿景上来,为了达到在开发团队中取得统一设计意图的效果,可以考虑援引团队设计模式。除此之外,针对性的项目前期培训也会是一种有效的做法。
使用架构模式
架构模式也是一种很好的架构愿景设计思路的来源。随着对设计模式的研究的深入,人们发现其中的一些设计模式可以扩展、或变化为软件设计的基础。在这个基础上再实现更多的设计,这些模式就形成了架构模式。当然,不同的软件,它们的架构模式也是不一样的。在《Applying Pattern》一文中,有一个很典型的架构愿景的例子:
假设我们需要设计分布式的交互式系统。分布式系统和交互式系统都有特定的架构模式,前者为Broker模式,后者为MVC模式。首先我们先要根据系统的特点的重要程度来排列模式的顺序。这里假设需求中分布式特性更重要一些。那么我们首先选择Broker模式作为架构的基本模式:

再考虑交互式的特点,根据MVC模式的特点,我们需要从目前的基本架构中识别出Model、Controller、以及View。Model和View都很简单,分别分布在上图中的Server和Client中。而Controller则有两种的选择,假设这里的Controller部署在客户端,上图则演化为下图:     

这样,基础的架构愿景就已经出现了。如果我们还有更多的需求,还可以继续改进。但是,记住一点,架构愿景不要过于复杂。正如我们在上一节中所讨论的,这里我们虽然是基于设计模式来讨论架构愿景,但是实际中还有很多从其它的视角来看待架构愿景的。至于要如何选择架构愿景的视角,关键的还是在于需求的理解。

No066-2.gif (5.88 KB, 下载次数: 79)

演化图:

演化图:

No066-1.gif (1.82 KB, 下载次数: 83)

Broker模式作为架构的基本模式:

Broker模式作为架构的基本模式:

论坛徽章:
0
8 [报告]
发表于 2003-08-27 08:49 |只看该作者

敏捷思维

敏捷思维-架构设计中的方法学(9)
分层 (上)
--------------------------------------------------------------------------------

来自:IBM China 作者:林星 [2003/04/17]
在定义了架构愿景之后,团队中的所有人员应该对待开发的软件有一定的了解了。但是,面对一个庞大的软件系统,接下来要做些什么呢?分而治之的思想是计算机领域非常重要的思想,因此我们也从这里开始入手。
要进行应用软件的设计,分层是非常重要的思想,掌握好分层的思想,设计出的软件是可以令人赏心悦目的。由于这一章的重要性和特殊性,本章的内容分为上下两节,并不采取模式描述语言的方式。
分层只是将系统进行有效组织的方式。 本章特别针对于企业应用进行讨论,但其中大部分的内容都可以应用在其它的系统中,或为其它的系统所参考。
在企业应用中,有两个非常重要的概念:业务逻辑和持久性。可以说,企业应用是围绕着业务逻辑进行开展的。例如报销、下订单、货品入库等都是业务逻辑。从业务逻辑的底层实现来看,业务逻辑其实是对业务实体进行组织的过程。这一点对于面向对象的系统才成立,因为在面向对象的系统中,识别业务实体,并制定业务实体的行为是非常基础的工作,而不同的业务实体的组合就形成了业务逻辑。
还有另一个重要的概念是持久性。企业应用中大部分的数据都是需要可持久化的。因此,基础组织支持持久性就显得非常的重要。目前最为通行的支持持久性的机制是数据库,尤其是关系性数据库-RDBMS。
除此之外,在企业应用中还有其它的重要概念,例如人机交互。
为了能够更有效的对企业中的各种逻辑进行组织,我们使用层技术来实现企业应用。层技术在计算机领域中有着悠久的历史,计算机的实现中就引用了分层的概念。TCP/IP的七层协议栈也是典型的分层的概念。分层的优势在于:
上层的逻辑不需要了解所有的底层逻辑,它只需要了解和它邻接的那一层的细节。我们知道TCP/IP协议栈就是通过不同的层对数据进行层层封包的,不同层间的耦合度明显降低。通过严格的区分层次,大大降低了层间的耦合度。
某一层次的下级层可以有不同的实现。例如同样的编程语言可以在不同的操作系统和不同的机器中运行。
和第三条类似的,同一个层次可以支持不同的上级层。TCP协议可以支持FTP、HTTP等应用层协议。
综合上面的考虑,我们把企业应用分为多个层次。企业应用到底应该分为几种层次,目前还没有统一的意见。
在前些年的软件开发中,两层结构占有很重要的位置。例如在银行中应用很广的大型主机/终端方式,以及Client/Server方式。两层的体系结构一直到现在还广泛存在,但是两层结构却有着很多的缺点,例如客户端的维护成本高、难以实现分布式处理。随着在两层结构的终端用户和后端服务间加入更多的层次,多层的结构出现了。
经典的三层理论将应用划分为三个层次:
表示层(Presentation Layer),用于处理人机交互。目前最主流的两种表示层是Windows格式和WebBrowser格式。它主要的责任是处理用户请求,例如鼠标点击、输入、HTTP请求等。
领域逻辑层(Domain Logic Layer),模拟了企业中的实际活动,也可以认为是企业活动的模型。
数据层(Data source Layer),处理数据库、消息系统、事务系统。
在实际的应用中,三层结构有一些变化。例如,在Windows的.NET系统中,把应用分为三个层次:表示层(Presentation Layer)、业务层(Business Layer)、数据访问层(Data Access Layer),分别对应于经典的三层理论中的三个层次。值得一提的是,.NET系统中表示层可以直接访问数据访问层,即记录集技术。在ADO.NET中,这项技术已经非常成熟,并通过表示层中的某些数据感知组件,实现非常友好的功能。这种越层访问的技术通常被认为是不被允许的,因为它可能会破坏层之间的依赖关系。而在Windows平台中,严格遵守准则就意味着需要大量额外的工作量。因此,我们看到准则也不是一成不变的。
在J2EE的环境中,三层结构演变为五层的结构。在表示层这里,J2EE将其分为运行在客户机上的用户层(Client Layer),以及运行在服务端上的Web层(Presentation Layer)。这样做的主要理由是Web Server已经成为J2EE中非常核心的技术,例如JSP和Java Servlet都和它有关系。Web层为用户层提供表示逻辑,并对用户的请求产生回应。
业务层(Business Layer)并没有发生变化,仍然是处理应用核心逻辑之处。而数据层则被划分为两个层次:集成层(Integration Layer)和资源层(Resource Layer)。其中,资源层并非J2EE所关心的内容,它可能是数据库或是其它的老系统,集成层是重要的层次,包括事务处理,数据库映射系统。
实例
这一章的的组织方式和之前的模式有一些差别。我们先从一个例子来体会架构设计中分层的重要性。

上图是一个业务处理系统的软件架构图。在上图中,我们把软件分为四个层次。这种层次结构类似于我们前面所谈的J2EE的分层。但是和J2EE不同的是,缺少了一个Web Server层,这是因为目前我们还没有任何对Web Server的需要。
在资源层上,我们有三种资源:数据库、平台服务、UI。数据库是企业应用的基础,而这里的平台服务指的是操作系统系统或第三方软件提供的事务管理器的功能。值得注意的是,这里的事务管理器指的并不是数据库内部支持的事务,而是指不同的业务实体间事务处理,这对于企业应用有着很重要的意义。因为对于一个企业应用来说,常常需要处理跨模块、跨软件、甚至跨平台的会话(Session),这时候,单纯的数据库支持的事务往往就难以胜任了。这方面的例子包括微软的MTS和Oracle的DBLink。当然,如果说,在你处理的系统中,可以使用数据库事务来处理大部分的会话的话,那就可以避免考虑这方面的设计了。除了典型的事务管理器,平台还能够提供其它的服务。对于大部分的企业应用来说,都是集成了多个的平台服务,平台服务对架构的设计至关重要。但是从分层的角度上考虑,上层的设计应该尽可能的和平台无关。和使用平台服务类似的,一般来说,企业应用都不会从头设计界面,大部分情况下都会使用现有的的UI资源。比如Window平台的MFC中的界面部分。因此,我们把被使用的UI资源也归到资源层这个层次上。
资源层的上一层是集成层。集成层主要完成两项工作,第一项是使用资源层的平台服务,完成企业应用中的事务管理。有些事务处理机制已经提供了比较好封装机制,直接使用资源层的平台服务就可以了。但是对于大多数的应用来说,平台提供的服务往往是比较简单的,这时候集成层就派上大用场了。第二项是对上一层的对象提供持久性机制。可以说,这是一组起到过渡作用的对象。它实际上使用的是资源层的数据库功能,并为上一层的对象提供服务。这样,上一层的业务对象就不需要直接同数据库打交道。对于那些底层使用关系型数据库,编程中使用面向对象技术的系统来说,目前比较常见的处理持久性的做法是对象/关系映射(OR Mapping)。
在这个层次上,我们可以做一些扩展,来分析层的作用。假设我们的系统需要处理多个数据库,而不同数据库之间的处理方式有一定的差异。这时候,层的作用就显示出来了。我们在集成层中支持对多个数据库的处理,但对集成层以上的层次提供统一的接口。对于业务层来说,它不知道,也不需要知道数据库的差别。目前我们自己开发了集成层中的持久类,但是随着功能的扩展,原有的类无法再支持新增加的功能了,新的解决方案是购买商用程序。为了尽可能的保持对业务层次的影响,我们仍然使用原有的接口,但是具体的实现已经不同了,新的代码是针对新的商业程序来实现的。而对业务层来说,最理想的状况是不需要任何的改变。当然现实中不太可能出现如此美好的情况,但可以肯定的一点是,引入层次比不引入层次要好的多。
以上列举的两个例子都是很好的解决了耦合度的问题。关于分层中的耦合度的问题,我们在下面还会讨论。
业务层的设计比较简单,暂时只是把它实现为一组的业务类。类似的,表示层的设计也没有做更多的处理。表示层的类是继承自资源层的。这是一种处理的方法,当然,也可以是使用关系,这和具体的实现环境和设计人员的偏好都有关系,并不是唯一的做法。在对软件的大致架构有了一个初步了解之后,我们需要进一步挖掘需求,来细化我们的设计。在前面的设计中,我们对业务层的设计过于粗糙了。在我们的应用中,还存在一个旧系统,这个系统中实现了应用规则,从应用的角度来看,这些规则目前仍然在使用,但新的系统中会加入新的规则。在新系统启用后,旧的系统中的规则处理仍然需要发挥它的作用,因此不能够简单的把所有的规则转移到新系统中。(有时候我们是为了节省成本而不在新系统中实现旧系统的逻辑)。我们第二步的架构设计的细化过程中将会加入对新的要求的支持。
在细化业务层的过程中,我们仍然使用层技术。这时候,我们把原先的业务层划分为两个子层。对于大多数的企业应用来说,业务层往往是最复杂的。企业对象之间存在着错综复杂的联系,企业的流程则需要用到这些看似独立的企业对象。我们希望在业务层中引入新的机制,来达到组织业务类的目的。业务层的组织需要依赖于具体的应用环境,金融行业的应用和制造行业的应用就有着巨大的差距。这里,我们从一般性的设计思考的角度出发来看待我们的设计:

No067-2.gif (19.47 KB, 下载次数: 56)

No067-2.gif

No067-1.gif (9.04 KB, 下载次数: 60)

业务处理系统的软件架构图

业务处理系统的软件架构图

论坛徽章:
0
9 [报告]
发表于 2003-08-27 08:50 |只看该作者

敏捷思维

首先,我们看到,业务层被重新组织为两个层次。增加层次的主要考虑是设计的重用性。从我们前面对层的认识,我们知道。较高的层次可以重用较低的层次。因此,我们把业务层中的一部分类归入较低的层次,以供较高的层次使用。降低类层次的主要思路是分解行为、识别共同行为、抽取共性。
在Martin Fowler的分析模式中提供了一种将操作层和知识层分离的处理方法:


Action、Accountability、Party属于较高层次的操作层(Operational Layer),这一层次的特点是针对于特定的应用。但是观察Accountability和Party,有很多相似的职责。也就是说,我们对于知识的处理并不合适。因此,Martin Fowler提出了新的层次――属于较低层次的知识层(Knowledge Layer)。操作层中可重用的知识被整理到知识层。从而实现对操作层的共性抽取。注意到虽然图中的层次(Level)的概念和层(Layer)有所差别,但是思路是基本一致的。
另一种分层方法是利用继承:     


该图中也是来自于分析模式一书。不同的部门有着差异点和共性,将共性提取到父类中是继承的基本概念。这时候我们可以把父类看作是较低的层次,而把子类看作是较高的层次。对于一组层次结构很深的类来说,也可以从某一个水平线上分离层次。
最后一种方法需要分解并抽象对象的行为。C++的STL中为不同的数据类型和不同的容器实现了Iterator的功能。它的实现是典型的降低层次的行为。我们不考虑它对不同数据类型的支持,因为它使用了比较独特的模板(Template)技术,只考虑它对不同的容器的实现。首先是对共性的分析,不论是数组(Array)还是向量(Vector),要执行遍历操作,都需要知道三个条件:容器的起始点、容器的长度、匹配值。这就是一种抽象。通过这种方式,就可以统一不同容器的接口。
以上我们讨论了细分层次的好处和实现策略。下面我们回到前例中,继续讨论层中的各个部件。
业务实体指的是企业中的一些单独的对象。例如订单、客户、供应商等。由于业务实体可以被很多的业务流程(业务会话)所使用,因此我们把业务实体放在业务实体层中。企业中的业务实体大多是需要持久性的。因此在我们的设计中,业务实体将持久性的职责委托给下一个层次中的持久性包,而不是直接调用数据库方法。另一种常用的方法是使用继承――业务实体继承自持久类。EJB中的Entity Bean就是典型的业务实体,它可以支持自动的持久性机制。
在我们的系统中,规则是一个尴尬的存在。部分的规则处于旧系统中,并使用旧系统的持久性机制。而新系统中有需要支持新的规则。对于上层的用户来说,并不需要知道新旧系统的规则。如果我们在使用规则时做一个新旧规则的判断并调用不同的系统函数,那就显得太傻了。我们的设计对上层而言应该是透明的。
所以我们总共设计了三个包来实现规则的处理。包装器包装了旧系统中的规则,这样做处于几点考虑:首先,旧系统是面向过程的,因此我们需要用包装器将函数(或过程)封装到类中。其次,对旧系统的使用需要额外的程序(或平台服务)的支持,因此需要单独的类来处理。最后,我们可以使用Adapter模式[GOF 94]将新旧系统不同的接口给统一起来,以使他们能够一起工作。新的规则类实现了新的规则,较好的做法是定义一个虚协议,具体的表现形式可以是虚基类或接口。然后由其子类实现虚协议。再结合前面介绍的把旧规则的接口转换为新的接口的方法,就能够统一规则接口。从图中我们看到,业务实体需要使用规则,通过统一的接口,业务实体就能够透明的使用新旧两种规则。
在定义了规则和标准的规则接口之后,上一层的规则控制器就可以通过调用规则,来实现不同规则组合。因此这个规则控制器就类似于应用规则。在业务交易处理中需要调用规则控制器来实现部分的功能。
请注意,这里讨论的思路虽然非常的简单、清晰,但是现实中的处理却没有这么容易。因为为新旧规则制定统一的接口实在是太难了。要能够使接口在未来也相对的稳定更是难上加难。而在应用已经部署之后,对已发布的接口的任何一个小改动都意味着很高的成本。在这个问题上,并没有什么好的策略,经验是最重要的。在实际中的一个比较实用的方法是为接口增加一些额外的参数。即便可能这个参数当前并没有使用到,但是如果为了有可能使用的话,那还是加上吧。举个例子,对于商业应用软件而言,数据库,或者说是关系数据库一定是不可缺少的部分。大量的操作都需要和数据库结合起来,因此,可以考虑在方法中加入一个数据库连接字符串的参数。虽然这对于很多方法而言是没什么必要的,但是为将来的实践提供了一个可扩展的空间。国内的某个知名的ERP软件的设计就采用了这种思路。
本章的主要精力都放在实例的研究上,在有了一个基本的概念之后,我们再回来谈谈分层的一些原则和需要注意的问题。

No067-4.gif (17.08 KB, 下载次数: 56)

利用继承

利用继承

No067-3.gif (17.47 KB, 下载次数: 56)

Martin Fowler的分析模式

Martin Fowler的分析模式

论坛徽章:
0
10 [报告]
发表于 2003-08-27 08:53 |只看该作者

敏捷思维

敏捷思维-架构设计中的方法学(10)
分层 (下)
--------------------------------------------------------------------------------

来自:IBM China 作者:林星 [2003/04/17]
上篇我们用了大量的篇幅来观察了一个实际的例子,相信大家已经对分层有了一个比较具体的概念了。在这一篇中我们就对分层在实践中可能会遇到的问题做一个讨论。分层在架构设计中是一种非常常见的,但是又很不容易用好的技术。因此我们这里花了很大的气力来讨论它。
由于这是一篇介绍软件设计技术的文章,为了尽可能让更多的人理解,本应该尽可能不涉及到过于具体的技术或平台。但是这个目标可能很难实现,因为软件设计是没办法脱离具体的实现技术的。因此本文能够做到的是尽可能的不涉及具体的编码细节。
何时使用分层技术?
分层技术实际上是把技术复杂化了。和以往简单的CS结构的系统不同,分层往往需要使用特定的技术平台来实现。当然,不使用这些技术平台也是可能的,但是效果可能就没有那么好了。支持分层技术的平台有很多,包括目前主流的J2EE和.NET。甚至在不同厂商的开发平台上,要求也不一样。使用分层技术实现的多层架构,成本要比普通的CS架构高得多。
这就产生了一个非常现实的问题-并不是所有的软件都适合采用分层技术的。一般来说,小型的软件使用分层并没有太大的意义,因为分层导致的成本超过它所能带来的好处。在一般的CS结构中,可以把界面控制、逻辑处理和数据库访问都放在一块儿。这种设计方式在纯粹的多层主义者看来简直就是十恶不赦。但是对于小型的软件而言,这并不是什么大不了的事情。因为从表示层到数据层的整套功能都被囊括在一个功能块中,同样能够实现较好的封装。而且,如果结构设计的足够好,也能够避免表示层、业务层和数据层之间出现过高的耦合度。因此,除非确实需要,不然没有必要使用分层技术。
尤其在处理一些特殊的项目时,严格的区分三层结构并不理想。比如在快速开发windows界面的应用时,往往会用到一些对数据库敏感的控件,这种处理方法跨越了三个层次,但是却很实用,成本也比较低。又比如一些框架,给出了从界面层到数据库的综合的解决方案,和windows的应用类似,严格的三层技术也不适用于这种情况。
如何使用分层技术?
从某种意义上来看,层其实是一个粗粒度的组件。就像我们使用组件技术是为了对系统进行一种划分一样,层的一个很大的作用也是如此。其目的是为了系统更容易被理解,不同的部分能够被较容易的替换。
使用分层技术的依据是软件开发人员的实际需要。如果你是在使用某些优秀的面向对象的软件开发平台的话,那它们一般都会建议(或是强制)你使用某一种分层机制。这是你采用分层技术的一大参考。
对于大多数有一定经验的软件团队而言,一般都会积累一些软件开发经验。其中包含了很多在某些特定的领域中使用的基础的类或组件。这些元素构成了一个系统的通用层次。这个层次也是分层时需要考虑的。例如一些应用软件中使用的一些通用的Currency对象或是Organization对象。分析模式一书对此类的对象进行了充分细致的阐述。这个层次一般被称为跨领域层(cross-domain layer),或称为工具层(utility layer)。
目前的很多软件都采用了数据库映射技术。数据库映射层对于企业应用系统非常的重要,因此也需要纳入考虑之列。数据库映射技术用起来简单,但是要实现可不容易。如果不是非常有必要,尽可能使用现成的框架,或是采用其中部分的设计思路。试图构建一个大而全的映射层次的代价是非常高昂的,认识不到这一点会带来很大的麻烦。数据库映射技术的知识,我们在下文中还有专门的篇幅来讨论。
如何存放数据(状态)?
在学习EJB的过程中,最先要理解的一定是有状态和无状态的概念。可以说,整个概念是多层体系的核心。为什么这么说呢?这里的状态指的是类的状态,例如类的属性、变量等。由于状态的不同,类也表现出差异来。而对于多层结构的软件,创建和销毁一个类的开销是很大的,如果该软件支持分布式的话尤为如此。所以如果系统的不同层次间进行频繁的调用-创建一个类,再销毁一个类。这种做法是非常消耗资源的。在应用系统的设计中,一般不单独使用COM,就是这个原因。所以我们很自然的想到了一种经典的设计-缓冲池。把对象存放在缓冲池中,当需要的时候从池中取出一个,当不需要的时候再把对象放入池中。这种设计思路能够大幅度的提高效率。但是这对能够放在池中的对象也提出了苛刻的要求-所有的对象必须是无差异的,也就是无状态的。只有这样才能够实现缓冲池。
一般来说,对象缓冲池的技术是用在中间的业务层上的。既然中间业务层上不能够保留有状态,那就出现了一个状态转移的问题。这里有两种的选择,一种是前移,把状态移到用户端,最典型的是使用cookie。这种选择一般是由于状态和用户端有关,不需要长时间保存。另一种选择是后移,把状态移到数据层,由数据库来实现持久性状态,当需要时才把状态提交给业务层。这种方式是企业应用软件中采用最多的,但是也增大了数据库的负担。
处理好接口
由于使用了分层技术,因此原先那种在CS结构中类之间存在复杂关系就有必要重新评估了。一般层间的耦合度不宜过大。因此需要慎重的设计层之间的类调用方式。一些分布式软件体系(例如J2EE)对层之间的调用方式以接口的形式给出了要求。同时,不同层之间仅仅知道目标层的接口,而不知道目标层的具体实现。EJB的home接口和remote接口就是这样。在COM+体系中,也需要在设计类的同时,把接口公布出来,以供客户方使用。
在设计层间的接口时,除了考虑开发平台的约束之外,还有一点是开发人员必须考虑的。那就是业务需要。业务层中往往有非常多的对象和方法,它们之间的关系也非常的负责,但对于其它的层次来说,它并不关心这些细节。因此业务层公布的接口必须要简单,而且和实现无关。因此,可以使用设计模式的Facade模式来简化层间的接口。这种做法非常有效,EJB中的SessionBean和EntityBean区分就含有这种设计思路。
同样的,不同层之间的数据传递也存在问题。如果不同层的物理节点在一起还好办,如果不在一起,那就需要使用到分布式技术了。因为不同机器的内存地址编码是不同的,如果接口之间采用对象引用的方式,那一定会出现问题。因此会将对象打包成字符串,发送到目标机器后再还原为对象。所有的分布式平台都提供了对这种技术的支持,这里就不多说了。但是这种实现技术会对我们的设计思路产生影响,少量的数据直接使用字符串来传递,数据量大的话,就需要使用封装了数据的对象。这类对象的设计需要非常的小心。在设计过程中可以参照开发平台提供的一些标准做法。同样的,数据的请求的频率也是难点之一。过于频繁的操作来自后端的数据会加大系统的开销。因此,在设计调用方法时同样需要结合实际应用来考虑。
兼顾效率
一般来说,纯粹的面向对象设计者设计出的软件都会比较完美。但是需要付出一定的代价。在一些大的软件平台上编程的时候,往往需要利用到平台的一些机制。最典型的就是平台的事务机制(最典型的包括J2EE平台的JTS,以及COM+平台的MTS),但是事务机制的实现往往需要平台大量对象的支撑。这种情况下,创建一个支持事务的对象的开销是很大的。处理这种问题有一种变通的办法,就是仅仅对需要事务支撑的对象提供事务支持。这就意味着,一个单独的业务实体类,可能需要根据是否支持事务分为两种类:对该业务实体的select方法不需要事务的支持,只有update和delete方法才需要有事务的支持。这是不符合纯面向对象设计者的观点的。但是这种做法却可以获得比较优秀的效率。


图1 将单个的业务实体分为不同的实现
应该承认,这种提高效率的做法加大了复杂度。因为对于客户端来说,它们并不关心具体的实现技术。要求客户端在某一种情况下调用这个类,在其它情况下又调用另一个类,这种做法既不符合面向对象的设计思路,也增大了层间耦合度及复杂性。因此,我们可以考虑使用接口或是外观类(参见设计模式一书中的facade模式),把具体的实现封装起来,而只把用户关心的部分提供给用户。这方面的技巧我们在下面的章节中还会提到。
以迭代的方式进行分层
软件设计中的迭代做法同样可以适用于分层。根据自己的经验,在一开始就定义好所有的层次是很难的。除非有着非常丰富的经验,都则实现和原先的设计总有或大或小的差距。因此调整势在必行。每一次的迭代都能够对分层技术进行改进,并为后一个项目积累了经验。
这里的分层迭代不可以过于频繁,每一次的迭代都是对架构的重大修改,都是需要投入人力的,而且会影响到软件开发的进度。但是成功的迭代的效果是非常明显的,能够在接下来的开发周期中起到稳定架构,减少代码量,提升软件质量的功效。注意,不要让新潮技术成为分层迭代的推动力。这是开发人员都常犯的毛病,这并不是什么缺点,只能称为一种职业病吧。分层迭代的推动力应该源自于需求的演进以及现有架构的不稳定已经妨碍了软件进一步的开发。因此这需要团队中的技术主管对技术有着非常好的把握。
重构能够对迭代有所帮助。嗅出代码中隐藏的坏味道并加以改进。应该说,迭代是一种比较激烈的做法,更好的做法是在开发中不断的对架构、层次进行调整。但这对团队、技术、方法、过程都有着很高的要求。因此迭代仍然是一种主要的改进手段。
层内的细分
分层的思路还可以适用于层的内部。层内的细分并没有固定的方式,其驱动因素往往是出于封装性和重用的考虑。例如,在EJB体系中的业务层中,实体Bean负责实现业务对象,因此一个应用往往拥有大量的实体Bean。而用户端并不需要了解每一个的实体Bean,对它们来说,只要能够完全一些业务逻辑就可以了,但完成这些业务逻辑则需要和多个实体Bean打交道。因此EJB提供了会话Bean,来负责把实体Bean封装起来,用户只知道会话Bean,不知道实体Bean的存在。这样既保证了实体Bean的重用性,又很好的实现了封装。
面向接口编程
在前面的章节中,我们提到一个接口设计的例子。为什么我们提倡接口的设计呢?Martin Fowler在他的分析模式一书中指出,分析问题应该站在概念的层次上,而不是站在实现的层次上。什么叫做概念的层次呢?简单的说就是分析对象该做什么,而不是分析对象怎么做。前者属于分析的阶段,后者属于设计甚至是实现的阶段。在需求工程中有一种称为CRC卡片的玩艺儿,是用来分析类的职责和关系的,其实那种方法就是从概念层次上进行面向对象设计。因此,如果要从概念层次上进行分析,这就要求你从领域专家的角度来看待程序是如何表示现实世界中的概念的。下面的这句话有些拗口,从实现的角度上来说,概念层次对应于合同,合同的实现形式包括接口和基类。简单的说吧,在概念层次上进行分析就是设计出接口(或是基类),而不用关心具体的接口实现(实现推迟到子类再实现)。结合上面的论述,我们也可以这样推断,接口应该是要符合现实世界的观念的。
在Martin Fowler的另一篇著作中提到了这样一个例子,非常好的解释了接口编程的思路:

interface Person {
  public String name();
  public void name(String newName);
  public Money salary ();
  public void salary (Money newSalary);
  public Money payAmount ();
  public void makeManager ();
}
interface Engineer extends Person{
  public void numberOfPatents (int value);
  public int numberOfPatents ();
}
interface Salesman extends Person{
  public void numberOfSales (int numberOfSales);
  public int numberOfSales ();
}
interface Manager extends Person{
  public void budget (Money value);
  public Money budget ();
}



可以看到,为了表示现实世界中人(这里其实指的是员工的概念)、工程师、销售员、经理的概念,代码根据人的自然观点设计了继承层次结构,并很好的实现了重用。而且,我们可以认定该接口是相对稳定的。我们再来看看实现部分:
public class PersonImpFlag implements Person, Salesman, Engineer,Manager{
// Implementing Salesman
public static Salesman newSalesman (String name){
  PersonImpFlag result;
  result = new PersonImpFlag (name);
  result.makeSalesman();
  return result;
};
public void makeSalesman () {
  _jobTitle = 1;
};
public boolean isSalesman () {
  return _jobTitle == 1;
};
public void numberOfSales (int value){
  requireIsSalesman () ;
  _numberOfSales = value;
};
public int numberOfSales () {
  requireIsSalesman ();
  return _numberOfSales;
};
private void requireIsSalesman () {
  if (! isSalesman()) throw new PreconditionViolation ("Not a Salesman" ;
};
  private int _numberOfSales;
  private int _jobTitle;
}



这是其中一种被称为内部标示(Internal Flag)的实现方法。这里我们只是举出一个例子,实际上我们还有非常多的解决方法,但我们并不关心。因为只要接口足够稳定,内部实现发生再大的变化都是允许的。如果对实现的方式感兴趣,可以参考Matrin Fowler的角色建模的文章或是我在阅读这篇文章的一篇笔记。 通过上面的例子,我们可以了解到,接口和实现分离的最大好处就是能够在客户端未知的情况下修改实现代码。这个特性对于分层技术是非常适用的。一种是用在层和层之间的调用。层和层之间是最忌讳耦合度过高或是改变过于频繁的。设计优秀的接口能够解决这个问题。另一种是用在那些不稳定的部分上。如果某些需求的变化性很大,那么定义接口也是一种解决之道。举个不恰当的例子,设计良好的接口就像是我们日常使用的万用插座一样,不论插头如何变化,都可以使用。
最后强调一点,良好的接口定义一定是来自于需求的,它绝对不是程序员绞尽脑汁想出来的。
数据映射层
在各个层的设计中,可能比较令人困惑的就是数据映射层了。由于篇幅的关系,我们不可能在这个问题上讨论太多,只能是抛砖引玉。如果有机会,我们还可以来谈谈这方面的话题。
面向对象技术已经成为软件开发的一种趋势,越来越多的人开始了解、学习和使用面向对象技术。而大多数的面向对象技术都只是解决了内存中的面向对象的问题。但是鲜有提到持久性的面向对象问题。
面向对象设计的机制与关系模型有很大的不同,这造成了面向对象设计与关系数据库设计之间的不匹配。面向对象设计的基本理论包括耦合、聚合、封装、继承、多态,而关系数据模型的理论则完全不同,它的基本原理是数据库的三大范式。最明显的一个例子是,Order对象包括一组的OrderItem对象,因此我们需要在Order类中设计一个容器(各个编程语言都提供了一组的容器对象及相关操作以供使用)来存储OrderItem,也就是说Order类中的指针指向OrderItem。假设Order类和OrderItem分别对应于数据库的两张表(最简单的映射情况),那么,我们要实现二者之间的关系,是通过在OrderItem表(假设名称一样)增加指向Order表的外键。这是两种完全不同的设置。数据映射层的作用就是向用户端隐藏关系数据库的存在。
自己开发一个对象/关系映射工具是非常诱人的。但是应该考虑到,开发这样一个工具并不是一件容易的事,需要付出很大的成本。尤其是手工处理数据一致性和事务处理的问题上。它比你想象的要难的多。因此,获取一个对象/关系映射工具的最好途径是购买,而不是开发。
总结
分层对现代的软件开发而言是非常重要的概念。也是我们必须学习的知识。分层的总体思路并没有什么特别的地方,但是要和自己的开发环境、应用环境结合起来,你还需要付出很多的努力才行。
在完成了分层之后,软件架构其实已经清晰化了。下面的讨论将围绕着如何改进架构,如何使架构稳定方面的问题进行。

No068-1.gif (2.62 KB, 下载次数: 58)

图1 将单个的业务实体分为不同的实现

图1 将单个的业务实体分为不同的实现
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP