对象技术OOA/OOD

在设计一个应用程序系统过程中,用例编写是非常重要的,需要抓住参与者及其目标,采用涉众—目标—用例列表的形式将需求细化。

根据用例的描述,专家的建议,以及重用现有模型来创建领域模型,其难点在于抽象概念类。根据领域模型、用例来创建系统顺序图明确系统操作(一般对应用户输入),有必要时还需要为系统操作创建操作契约(详述最复杂和微妙的系统操作)。

然后进入对象设计阶段,主要关注的是领域层,而在UI层,领域层,技术服务层之间是通过接口来通信的。对象设计的重点在于动态类图的创建(也称为交互图,其阐述了OO设计细节。如消息的发送,发送给谁,发送顺序等具体问题)

交互图是静态类图的输入制品,静态类图中的软件类源于领域模型中的概念类,阐述了领域对象之间的关系,其主要分为以下4中关系:

1.      泛化

在DCD(设计类图)软件视角的类图中,其意为超类到子类的继承

2.      关联

在DCD软件视角的类图中,其表示目标对象为源对象的属性,目标对象可以独立存在

3.      依赖

Uml中表示为,客户元素(任何类,包括类、包、用例等)了解其他的提供者元素。在类图中,使用依赖线描述对象之间的全局变量、参数变量、局部变量和静态方法(对其他类的静态方法加以调用)的依赖。

4.      组合

表示一种更强的关联关系,组合强调整体与部分的关系,部分不能独立于整体而存在,如手与手指之间的关系

对象设计的难点在于如何分配对象职责,一般遵循GRASP(通用职责分配软件模式)的准则,其具体细节可参阅UML和模式应用

在整个系统设计过程中,领域模型的创建,对象职责的分配是最重要也是最困难的两个方面,初学者尽管明白整个设计过程,但是因为经验的缺乏,很难设计出良好的(指可维护,可复用,可拓展)应用程序系统。

 

属性可见性与参数可见性

对象A给对象B发送消息,那么A必须具有对B的可见性

通常B为A的属性或B为A接口的参数是最常见的情况。关键问题是如何决定B到底是应该为A的属性,还是为A接口的参数?

我们需要调用对象B的服务,是将B作为A的属性,还是作为参数传递给A的接口呢?

通常如果A组成聚合B,那么B应该为A的属性,这是很显然的。如果A依赖B,那么既可以将B作为A的属性,也可以将B作为参数进行传递给A。

对象Hand是对象People的一部分,所以对象People是组成聚合Hand对象的,这是一种组成聚合关系,人拥有手,手不能独立于人单独存在。对象People具有对象Hand的属性可见性,对象People执行ReachOutHands()操作需要给对象Hand发送Open()消息。

对象People与对象Cup无组成聚合关系,对象People执行DrinkWater()操作,需要依赖对象Cup,我们既可以将Cup对象作为People对象的属性,也可以将其作为参数传递给对象People的接口。当然我认为作为参数进行传递更合适一些。

因为人是依赖水杯的,而不是说人有一个水杯,并不是每个人都有一个水杯,这是现实世界的表示。然而在软件的世界里,将对象Cup对象作为People对象的属性也是很常见的行为,特别是与一些非领域对象的便利类对象进行交互。

再看对象Cup与对象Water,对象Cup是作为容器,而对象Water是对象Cup中的元素,这种情况一般将元素对象作为容器对象的属性。这是一种关联关系,元素对象可以独立于容器对象单独存在。

 

依赖于接口而非依赖于实现

接口就是对象能响应的请求的集合,也就是这个对象操作的集合,操作型构即指C++中的类成员函数(signature)。一个消息对应一个操作型构,一个对象的类就定义类它所能接收到的所有消息。

实现就是指操作的实现,即指C++中类成员函数的定义。

理解这个概念必须先理解抽象类及其子类的关系。在C++中抽象类主要是提供一些接口,而其子类主要是继承这些接口并且重新定义实现。对象之间的消息传递是通过接口来传递的。一个对象具有某个接口,其他对象才能给它传递这个消息。因为抽象类及其子类遵循一致的接口,所以他们都能够接收相同的消息。

在抽象类中,一般不定义操作的实现,只是提供一些接口,由子类来具体定义实现,这样不同的子类就具有不同的实现。

所谓依赖于接口,就是指与抽象类耦合,所谓依赖于实现就是指与子类产生耦合。

那么这其中有什么区别呢?

在设计中,我们并不清楚子类的数目,因为子类的数目可能随着需求的进化而产生变化。具体的子类定义了操作的实现,与具体的子类耦合也就与具体的实现绑定在一起了。这样就被硬编码了,不能够在运行时期动态的改变实现。如果只与抽象类耦合,我们只是管理抽象类的指针,那么就在运行时能够灵活的指定不同的子类来实例化,这样就能动态的得到不同的实现。

目标是为了得到灵活的系统,因为抽象类只是提供类接口,与抽象类耦合就被称为依赖于接口。具体子类定义类操作的实现,耦合具体子类,就将实现定死了,不便于系统的灵活,所以称为依赖于实现。

变化的是实现,不变的是接口,与不变的耦合才能保证系统的最大灵活性。

标签