面向对象分析的概念模型和实现细节的不匹配

  面向对象的概念模型已经成为软件工程的标准分析模型,分析与实现之间的阻抗失配成为一个重要问题。而软件工程师却容易忽视这个问题。这里介绍将面向对象的概念模型使用现代的面向对象语言转换为一个具体实现面临的八个具体问题。我们还将提供一些例子展示如何开始解决这些问题。只有通过重新识别和定义希望我们能克服这些问题。

  概念模型是指面向对象的分析模型,实现环境是指面向对象编程语言,如C++ Smalltalk Java Ada95之类。

  在过去几年中软件工程的分析模型包含数据库的面向对象概念模型,这些新的模型允许我们以一种自然的方式思考问题,而这些模型更易于分析问题和创建规范,转换这些模型到有效的实现是困难的,在基本的分析概念和面向对象编程概念之间有阻抗失配情况。

  作为软件工程师最大问题是没有认识到这种阻抗失配,我们通常擅长解决我们看得见的问题,但是对于看不见的问题就不擅长了。

  解决阻抗不匹配的研究已经有很长一段时间,我们的目的是改善这种情况,一种办法是提高当前语言,这确实是一种可行的解决方案,但是我们这里还是针对当前今天的环境如何解决这种失配问题,因此,我们的解决方案是有关设计模式的研究,而不是有关语言编译器的研究。

1.持久性

  一个常见的对概念模型的假设是:所有对象都是持久的。对象只有因为被销毁或者他们自行终止才会不复存在。

  在实现环境中,当程序终止时所有对象都会死去。只有那些明确标识了的对象才会被持久化存储,程序员负责确保这些对象保存和程序调用需要时恢复。

  有三种基本的设计模式我们用来克这种模型之间的差异。第一个是使用当前技术处理持久性的模式,其他两个是改变我们整个设计理念,将可运作的设计程序更改为持久对象的程序设计。

  我们当前将一个假定持久的概念模型转换为面向对象的实现的技术第一步是首先确定需要持久的规范模型的组件,然后实现它们。实现类通常需要形式上继承一个"持久"类,该持久类定义了有关从磁盘存储和检索信息的方法。这种技术很好理解。现在所需要的是研究工具和方法使其更容易。

  其他两种方法解决持久问题的方法是,迫使我们认为程序是持久对象而不是瞬态transient函数。在第一种方法中,我们扩展当前手动选择存储和检索到对象自动存储和检索所有对象,除了那些被明确标识为瞬态(不需要存储)。因此,我们的一些概念框架将接近概念建模,所有事情都假定持久。第二种方法将程序作为与数据库相关对象的模式使用。也就是说,程序或执行对象将在持久的介质概念上执行。从逻辑上讲,在程序执行时,所有对象将从永久存储设备中读取和写入。当然,这导致效率问题。为了提高效率,我们将使用缓存技术,将这些需要执行的对象放入内存中。

2.类

  虽然许多实践认为类的概念和概念模型非常类似,面向对象编程语言中类或类型的概念其实和概念模型有区别,在概念模型中,类是一组对象的集合,在这个集合中,所有对象都有属性,每个对象都是类的成员,因为它们有显示的属性。然而,对象也可以有额外的属性,例如,类Teenager少年的一个成员可以拥有汽车,尽管这一属性并不是所有少年都有。概念模型中另外一个类属性是类迁移,可能一个对象成为或不再成为许多不同类的成员,某个时间对象也可能属于不止一个类。

  类的概念在当前实现环节(面向对象语言)是相当不同,一个对象不再是一个类的成员,而是一个类的实例,作为一个实例,它仅仅拥有那些在类中定义的属性,没有类的扩展,一个对象也不可能从一个类迁移到另外一个类,也不可能一个对象是不止一个类的实例。

  首先最简单的方式是我们能让编程语言的类或类型更加类似概念模型的类型,这可以通过一些扩展模式达到,也就是说,类可以是有一组对象的集合,我们也能让对象属于不止一个类的成员,能从类到类的迁移,这有两种方式:我们提供一种动态获取和删除属性的开发模式,在这个模式中,所有对象实现将是动态的数据结构(Go语言是彻底的面向组合的并发语言 )。第二种方式是我们确认都有足够空间存储所创建对象的可能所属类的成员的所有属性,这种模式需要一种路径分析一个对象所属类的最大集合,这些类的集合生命周期属于某个刚开始被创建类的实例。

3.关系

  概念模型和实现环境的一个明显区别是关系的存在,概念模型允许任何数量级的关系,每个类之间关系上的关联有着某种基数约束(cardinality constraint)。

  基数约束是指某一个关系只能有一定数量的关系,比如一个投票者Voter只能在选举中投票一次,这样需要在投票Voter到Election投票之间的关系VoteIn上有一个基数约束。(类似UML中1:N或N:1 一对多或多对一的基数约束)

  实现环境没有关系,相反,一个对象有属性,对象和属性之间并没有基本约束,因此,我们能发现很多关系实现都包含与更新异常有关的冗余。

  设计和实现之间的大量关系我们使用关系数据库的关系模型实现,也就是使用表和索引。

  小数量的关系,尤其是二元关系,我们需要模式或工具帮助将它们作为属性实现,为了实现的有效率的访问,相对于有效更新来说可能有些冗余,如果我们有一个关系是Student被分配作为Advisor,这样一个Student的实例将有一个Advisor的属性,而一个Advisor类将有一个属性Set_of_Students(学生集合,学生和Advisor是N:1关系)。可以使用一些工具防止更新异常。为关系开发的设计模式可以来确定一些附加信息用来优化它们,举例,如果我们知道关系是常数,我们就能在实现中使用冗余,因为更新异常不会发生,如果我们知道A和B之间二元关系,我们也从未通过B访问A,那么我们实现这个关系时,就将B作为A的一个属性来代表它们的关系。这些已经在UML这样的实现环境实现了。

4.一般/特殊

  概念模型包括一种抽象形式:一般或特殊,它代表类之间的"is-a"关系,如果一个对象是一个类的成员,C1是 类C的一个特殊,这个对象也是C的一个成员,因此,所有在C1中的对象都有C'的属性。

  在实现环境我们使用继承来实现这种关系,子类C1可以继承超类C意味着C1拥有C的所有属性和方法。

  在概念模型中,有可能可以使一个Teeager成为Person的特殊化,需要一个和Teeager关联关系Age,规定年龄13和19之间,在面向对象语言中,类似的表示需要age作为Teenager的属性类型,如Teenager_Age,继承实现来自Person的age属性如Person_Age,这是不可能做到的,只有一个办法是,当前面向对象语言能够支持动态更新和修改方法和属性,然后使用协变co-variance来确保,更新一个Teeager时,只有更新teeaager的age,如果语言不支持协变,这恐怕是一个大问题。

5. 活动对象Active Object

  在面向对象的概念建模,我们允许活动对象。一个活动对象有自己控制的线程。在一些概念模型所有对象被认为是积极的活动的,,除非他们被确定为被动passive对象。活动对象意味着对象内部存在并发性的。在概念建模中,并不认为活动对象应该提供统一的服务可用性。

  在面向对象的编程语言中所有的对象都是被动,且假定服务都是统一与可用的。(类似JavaEE/Spring架构)。

  大量工作都在努力实现活动对象,最重要的是使用并发性实现并发的活动对象,然而,我们仍然遇到两个问题:首先,许多并发设计是功能函数的并发,并不是基于对象的并发( Jdon框架力图在领域对象内实现并发),其次,大多数程序员不知道如何以并发方式思考,我们需要开发很自然让大家很舒服使用的模式,这些模式应该允许我们去思考并直接实现活动对象,正如过去我们使用“类Class”替代了“函数”作为程序主要构建方式(Java是以Class替代function),这样,活动对象能够替代传统的“任务task”,不仅我们应当拥有并发的活动对象,而且设计模式也应该支持对象内部并发性,我们也必须支持活动Active通讯,以事件和信号为特征的通讯,这种设计模式也应该允许非统一的服务可用。

6.并发性

  概念模型中有对象内部和对象之间的并发性,对象之间的并发性也应该包含进入活动对象,如果可能一个对象应该可以有超过一个的控制线程,用来实现内部对象并发,因为有这两种并发性,我们经常会发现大量并发的概念模型。

  在一些实现环境中,比如Smalltalk和C++,并没有并发性支持,其他Java Ada95 Eiffel是提供并发支持,但是这些语言过于细粒度,它们对一个大型对象支持有限数量的线程,比如十到几百,这个线程数量通过超过概念模型需要的数量。

  尽管Java和Ada95等做了大量工作实现并发性,但是还是很难用来创建并发性设计。

  我们需要开发这样一个设计模式,初始目标是让并发性从概念模型到编程语言的切换非常方便,包括分布式环境的对象之间并发和对象内部并发。

  我们还需要一种模式来降低大规模并发,解决大规模并发问题的方向可以参考将对象在一个大型互联服务器之间进行分布的方案,参考Web互联网方式,另外一个方法是尽可能多将普通被动对象作为活动对象。

7. 复杂的交互和通讯

  在不同的概念模型中有不同形式的通讯,通常提供同步和异步通讯方式,通讯采取事件或信号的形式,对应着信息输入和输出,为了描述复杂情况,模型提供实时的约束,为了将并发和通讯继承,概念模型也提供一种机制来处理错误的通讯,举例,如果一个对象试图和另外一个对象通讯,而另外一个还没有准备好通讯,那么分析员需要描述会发生什么。

  典型的支持通讯方式的实现环境是使用方法和函数调用,虽然这些环境支持并发提供了更高阶的通讯机制,但是通常都是同步的,大概只有Java真正是实现分布式环境的通讯。

  那些支持并发的语言还支持某种形式的通信,可以用来实现各种形式的复杂交互。Ada和Java可能是最好的。

  即使有着高级通信功能的最现代的实现环境,从概念模型直接映射到这些实现环境并不直接。实时约束、优先级和非确定性的映射问题必须降低。映射可能而且是最好能自动完成。因此,一个富有成果的研究成果是将概念模型自动翻译成交互的程序。

8.声明信息

  在概念模型有许多形式的声明性信息。有基数限制,实时约束,一般约束和派生描述,也有触发约束,例如类成员条件引起的触发,当一个不满足类C成员条件的对象满足了成为成员的条件,它就应该成为这个类的成员,例如当一个人person到62岁,他应该自动成为老年人,类似的暗示生命,当一个对象去除它属于类的所有属性,它也应该从这个类的成员中去除。

  面向对象的编程环境提供了一些方法来表示声明性信息。几乎每一个表达式是应用式的,只有Eiffel提供类不变性和具有前置条件和后置条件的方法。

  将声明性信息概念模型转化为应用实现要求我们解决两个问题。首先我们必须保证约束得到满足,我们必须推导表达式转化为代码。担保的限制,我们可以做两件事。首先我们可以鼓励发展的项目,我们可以证明一个约束总是感到满意。这是研究的正确性的证明。另一种方法是认识到约束中的对象,可能会改变。然后,无论在代码中哪个对象可能发生这种变化,需要确保程序能检查约束和采取适当的行动。

  我们使用后一种约束检查的方法去实现触发约束,对于一个给定的触发约束,我们需要确定任何变化的变量会引起约束改变其状态,识别出这种方法以后,我们会在它们返回之前修改修改它们,触发约束进行检查,如果由假变真,和触发约束相关的方法将会发出,当处理类成员条件时,该方法通常会引起对象被添加到相应的类中。

  软件开发的未来是分布式、并发、持久、活动对象系统。

原文英文

四色分析模型

领域驱动设计的优点和挑战