没有面向对象的模式设计的软件工程 (OOSE),就好像没有菜谱的烹饪一样。模式使用内容引导我们并且一步一步地对我们进行指导,来集合产生一个复发问题的解决方案。就像我们烹饪时需要菜谱一样,我们把模式作为可重复的,被证明过的东西来使用,并且使软件工程变得更加可靠和成功。
在烹饪中,剁和切在调制和调味菜肴中是必备的技术,同样,在所有种类的挑战中——包括基本的、中等的以及高级的,根据您的需要会有很多的设计模式。然而,食谱中经常包含一些辅助材料,其使得主菜更加美味,从而增强整个菜肴的质量。
这篇文章将会集中讨论这些模式之间的关系、组合以及变化。这就是我们常称的“基于模式的软件工程”。我提供的例子将会以形象的UML来显示,并且最终会被转化成为代码形式(例如Java语言)。由于模式不只是影响类和对象的结构,还有动态(操作),所以这篇文章将会对在面向服务的体系架构(SOA)中模式所承担任务的调研得出结论。
模式的概念
模式在软件工程中的崛起,使得我们开始注意到复现问题。如果你设计一个软件,遇到一个情形时你问自己:“Gee,我不是第一个面对这个问题的人!”你对于一个模式的研究就已经开始了。一旦你找到并使用一个模式,你的解决方案将不仅受益于从过去获得的知识,而且这个模式还为相关的模式打开了一扇门。一个单独的模式为它所描述的内容工作,并且提供更多的用来改进你的解决方案质量的各种相关的模式。最终,在一个设计中,模式可以成为一个整个基于模式的设计过程的开始点。
在我们讨论模式之间的关系之前,让我们再探究一下刚才提到的烹饪的比喻,来看一看一些单个的模式。
我将会描述一个典型的电视烹饪节目,来帮助我解释软件模式和他们之间的关系。电视节目的目的是演示一个菜肴的准备过程。在大多数的烹饪节目中,我们会在厨师前面找到杯子和碗,例如准备好的洋葱放在杯子里。这是因为专业的厨师不需要在电视观众面前演示如何切洋葱;这太令人感到无聊了。在录制电视节目之前,厨师已经让他的帮厨“切好了适量的洋葱”。这里的重点是,厨师不需要交流切菜的技术,而只需要告诉大家结果:一杯切好的洋葱。
软件工程师也使用这种基本的模式。这些模式中,例如General Responsbility Assignment Software Patterns (GRASP), 1 是最基本的模式,很多其他的模式都要用到它们。基本的设计模式组织并控制通讯或创建,或者他们在对象之间建立可视性。基本上,在一个面向对象的系统中,对象通过消息进行互相的交流。因此,所有这些消息(或者称作职责)需要被软件工程师分配,用来建立一个灵活的、可维护的系统。在这个事实的基础上,面向对象的软件工程师不断地问自己同样的一个基本问题:“谁应该和谁说话?”。
问题场景
在这篇文章的下面部分,我将会举例说明在一个时间表应用程序(根据时间表归属而变化)不断变化的场景中,使用各种模式的方法。图1显示了一个典型的面向对象设计者的情形,这是一个特殊的商业规定,它要求辨别时间表是否被分配。问题(“时间表被分配了么?”),回答(“是的”或者“没有”)需要被确定,但是问题仍然存在:谁来接受以及谁来发送这些消息?
图1:职责分配
甚至对于非常基础的设计情形,例如图1所描述的,我们也可以使用基本的设计模式;例如,GRASP模式。
在电视烹饪节目中,厨师使用了一个基本的模式——切好的洋葱——来汇集一个他自己的更加复杂的模式,他所做的菜肴。模式的级别已经从一系列单独的方面提高到了一整盘菜中,他包含不同的基本技术。食谱有一个名称;例如番茄酱。厨师的职责是决定使用和准备多少的洋葱。问题被移到一个更高的层次:从切洋葱到做出一份美味的番茄酱。厨师开始使用他自己的模式——食谱,其中包含很多其他的模式(例如炒,切芹菜等等)。经验丰富的厨师会在特定的情况使用特定的模式,来表现出菜肴色泽,纹理和模式的精细。
软件设计模式是不同的,除了基本的GRASP模式外,工程师还会使用更多的被提升的模式,例如Gang of Four 2 (GoF)或者架构模式。现在大部分从大学毕业的软件工程师都是基于面向对象的原理,软件开发行业已经开始提升模式的层级,从问题解决技术层级提升到问题预防技术层级。我将会使用设计模式——可重用对象 (来自Gang of Four)作为一个设计模式来演示模式之间的关系,并且使用IBM Rational Software Architect (RSA) 模式举例说明。
让我们回到最初的场景,请看图1,我们打算基于分配过程建立一个时间表应用程序。设计人员需要分辨时间表是否被分配。在这个场景中,只需要填加一个叫做isApproved的属性到Timesheet对象,它包含一个布尔型的值true或者false。这个解决方法的问题是,对象的属性可能会改变,依靠属性的内容,我们需要决定将要发送的消息的类型。如果我们想要添加另外一个选项——例如,Submitted——布尔型属性,它拥有两个值true或者false,就不再需要提供这个设计方法了。由于介绍了Submitted声明,最初的设计(为两个值建立的)将会暂停并且整个业务逻辑需要我们再次评估最初的设计。
稍后我将会演示当模式被使用时,从一个两个状态设计到三个状态设计的转换是多么的平滑。如图2所示,我们的新设计方法将会违反两个基本的设计模式,Expert和Polymorphism, 3 并且没有必要将一个对象和另一个对象的业务逻辑结合在一起。
布尔值方法将不仅会违反基本的设计模式,同时由于Timesheet对象可以轻松的暂停,并且整个对象需要根据每一个变化而重新测试,因此它还会增加软件工程师的维护负担。
图2:一个违反Expert和Polymorphism模式的UML图
把图2转换成UML设计将会产生一个类似于图3的Java结构。
图3:一个Java的例子,违反Expert和Polymorphism模式
一个解决方案:State 模式
GoF模式目录为我们的设计挑战提供了一个可能的解决方案。这个模式叫做State。
首先我们需要验证这个模式是否满足我们的需求,并且阅读模式的目的,应用和结论部分。因为模式要求它“允许一个对象在它的内部状态变化的时候改变它的行为。对象将会出现改变它的类[GoF]”,我们继续并应用这个模式到我们的问题中。
使用State这个模式的一个好处是,它可以通过孤立各种状态来分解图3中if语句的情景。UML状态机符号帮助我们描述并研究了各种状态。最初我们的时间表非常简单,并且我们从存在的结构中隔离了两个状态,Approved和NotApproved。
图4:时间表的UML状态机图表(两个状态)
取代询问对象的哪一个值嵌套在一个属性中(我们这里是isApproved)以及根据这个做决定(违反polymorphism的原则),我们现在可以告诉对象去做什么,简单的发送消息给它并且让Timesheet对象来处理事件。我们想要设计的是发送消息的某种方式,如下图所示,ts是一个Timesheet对象。
图5:时间表的新职责分配(Java)
在我们隔离各种状态之后,从Timesheet对象中移除if结构,然后分配三个职责(开始,批准和拒绝),我们想要应用State模式到我们的解决方案中。使用RSA模式浏览器导航到State模式,它显示了我们的模式中参与的类。
图6:State模式和RSA模式浏览器中参与的类
为了获得State模式的整体构架视图,模式浏览器提供给我们下列布局图:
图7:RSA模式浏览器中的State模式构架
State模式的cookie-cutter解决方案需要被调整成为适应我们的应用程序细节的需求。在从模式浏览器直接拖拽模式到我们的工作台之后,我们可以从应用程序细节类模块中分配参加的类。下列的图表包含一个作为内容对象的Timsheet模式,State的Java接口ITimesheetState以及来自我们时间表应用程序(Approved和NotApproved)的两个具体状态。
图8:RSA中应用的状态模式
这个模式的Java预览图如图9所示。在消息approve()被发送到Timesheet对象之后,它获得消息并且委派它到它的状态,并提供一个回指自身的指示器(this参数)。
图9:从内容到状态对象的消息委托
在approve(this)消息被发送之后,处于运行时间的状态被定位于State对象,它将会处理事件(它是真实的多态)。例如,NotApproved状态将会执行approve(ITimesheetState state)消息,如下所示:
图10:固化状态方法实现——未批准
为了支持多态方法,我们需要指派分配职责,也就是批准状态,即使在这个特定情形中我们什么也不需要做。
图11:固化状态方法实现——已批准
现在State模式[GoF]已经被分配了,让我们看看在我们的模式设计中发生了什么,是否发生了一个需求的变化:例如,投资人需要在时间输入并且请求确认之后能够递交他们的时间表。下列的状态机图表显示了两个新的状态,开始和提交,他们代替了之前的未批准状态,来适应这个需求变化。
图12:时间表的UML状态机(多种状态)
图13中的UML Design Class图表描述了由新的需求引起的类模块的变化。即使新状态类和消息已经被提出并且一个状态已经被移除,变化仍然是易于管理的。最重要的一点是Timesheet并没有被改变。它仍然持续传送它所收到的所有信息到它的实际状态。这对于我们图3中的if-else结构是一个巨大的改进,因为在测试视图中所涉及的领域,已经从Timesheet对象转移到了它的状态中。
图13:部分UML设计类图表(时间表和新状态)
基于模式的开发
之前,我阐述了一个设计难题,并对其应用了一个普通解决方案。(State 模式), 同时指出使用模式(可维护性和弹性)的优势所在。 在基于模式的解决过程中,设计者不仅在出现问题时要使用模式,还要使用模式去驱动全部的设计。这种方法有稍些不同,因为它以设计者正积极与模式目录互动并使用模式间的关系作为前提的。通常通过选好的样板为带有目录的模式分组。GoF模式模板,例如,含有 命名和分类,目的,别称,动机,适用性,结构,参与者,协作,结论,实施,范例代码,已知使用 ,最后至少还要有,相关模式。
在模式模板中的相关模式部分中含有可以在项目中使用的其它模式的重要线索。例如,通过模式目录, State 模式经常涉及到Flyweight 和 Singleton[GoF]。这个信息必定会对软件引擎引发一些列新的问题——例如, “存在既可以从Flyweight 也可从 Singleton中获益的解决方案么?” ——并且可以使引擎检查存在的方法。
使用State模式的解决方案有个缺点。假如我们的时间表系统处于操作状态,例如在已批准状态下分配5000个时间表,这时我们就拥有5000个已批准状态实例。同样,当每个状态改变时,我们将为新状态创建一个新的实例并且Java垃圾收集器将收集旧的状态对象。这对于时间表应用程序也许不是十分关键的,但在其他情况下,这将花费很大的资源。图14仅仅显示了少数时间表和关联状态,它们可以在应用程序中增长成千上万倍。
图14:在Flyweight模式之前的对象模型
在时间表例子中,“被证实的,提交的,和进入的”状态是 Flyweight对象的优秀候选者,因为这里不需要为其中一个状态增加附加的属性以便区分这些实例。 使用Flyweight 模式,我们可以进一步改进状态模式解决方法,如图15所示。
图15:使用Flyweight后的对象模型
我们的时间表在任何时刻仅仅包含3个不同状态实例,它们增强了可维护性和性能。
但是...... 应用Flyweight [GoF] 模式给设计者带来了新的挑战。取代了生成一个新的特殊状态对象实例或者把flyweight对象作为参量,我们要在这个特殊情况下实现这个状态对象。Singleton [GoF] 模式正好可以完成这个意图。我们既可以执行每个状态作为Singleton,也可以创建一个状态工厂用于生成和管理状态。后面的方法将进一步通过分离关系来提高可维护性。
在基于模式的开发中,目的地也许就是新的模式的起源。例如,状态模式经常与Flyweight 和 Singleton 模式相互协调。 然而, Flyweight 和 Singleton与其他模式联系的更加紧密,等等。简而言之,我把对这些模式之间的阐述限制在GoF 目录;模式模板的 Also Known As部分也为其它的目录敞开了大门。
在面向服务的体系架构中的基于模式开发
在 SOA 中,包含了服务,服务提供者和消费者,就如同这里存在责任,接受者和发送人。不同的是,应用软件设计中心方法被抬高到服务的高度,它对准了拥有商业步骤的IT系统。迄今为止,我们对时间表应用程序的设计已经由技术和变化的需求所驱动。为了在SOA中包含时间表应用程序,服务必须显露,这样人工和非人工界面将会消失。
在SOA设计中,时间表应用程序这个独立的观点将被时间表应用程序如何与其他应用程序的服务协调的观点所取代。依靠商业需求,时间表应用程序可以看成组织的薪水册程序或者与成本细目分类结构结合用于产品管理。对于企业和应用程序的设计师来说展示或修改已存在的服务以及生成新的服务成了关键的任务。因此利用模式建立一个更灵活和可维持的系统成为SOA成功的关键因素。
在本文的前面,我展示了模式是如何促进附加相关模式的应用程序,甚至穿过目录。我们也看到了模式以不同的形式存在,从帮助设计对象层的决定到帮助设计应用层的模式(也就是,分配职责与GoF模式)。基于模式的方法在SOA中帮助提供灵活的以及可维持的服务,并且SOA自身可以驱动并且激励基于模式的方法。商业建模模式以及通过SOA激发的体系架构/网络模式可以自上而下地驱动模式/驱动开发,反之,基于模式的应用程序设计准备了一个成功的自下而上的SOA 。
结论
找到一个和问题匹配的模式不仅仅是引进了一个接解决问题的方法,而且还意味着一个新的研究和对有关模式的更深入的评估。如果你使用过因特网的搜索引擎去搜索一个你不太清楚的话题,此研究/发现/探索的行为方式你应当很熟悉。你经常从一个你不确定的术语出发,但是随着你看见更多的已接受术语和在你的结果集中未打开的知识领域时,你可以深入了解模式的思想和解决方案。不久,你就可以增强你自己的问题,最终扩展你的原始想法。
当正确地进行编目之后,模式提供了一个公共的路标,鼓励工程师调查有问题的地方,更重要的是,它让我们应用一套行之有效的方法而不是单单一种方法。在上文中,我使用了一个基本的增进请求,阐述了改变对处于维护模式下的系统的基于模式的解决方法的影响。随后,反复—渐进过程模型,在创建过程中项目通常面对非常相似的情形,并且,我们可以很容易的看到利用基于模式的开发对改善工程所带来的好处。
Rational Software Architect (RSA) 通过普通设计模式(例如GoF)或创建自身的模式目录来支持基于模式的工程过程。在企业级,发布RSA的模式目录以及共享模式库可以提高通过率,可以使得IT设计更加可靠与灵活。反映和适应组织变革的能力是SOA的基本策略。