OOAD - 面向对象设计
在分析阶段之后,概念模型使用面向对象设计(OOD)进一步发展为面向对象模型。 在 OOD 中,分析模型中独立于技术的概念被映射到实现类,识别约束并设计接口,从而产生解决方案域的模型。 简而言之,构建了详细的描述,指定如何基于具体技术构建系统
面向对象设计的阶段可以确定为 −
- 系统上下文的定义
- 设计系统架构
- 系统中对象的识别
- 构建设计模型
- 对象接口规范
系统设计
面向对象的系统设计涉及定义系统的上下文,然后设计系统的架构。
上下文 − 系统的上下文有静态和动态部分。 系统的静态上下文是使用整个系统的简单框图来设计的,该框图扩展为子系统的层次结构。 子系统模型由UML 包表示。 动态上下文描述了系统如何与其环境交互。 它使用用例图进行建模。
系统架构 − 系统架构是根据架构设计原则和领域知识,基于系统上下文进行设计的。 通常,系统被划分为多个层,每个层又被分解以形成子系统。
面向对象的分解
分解是指根据分而治之的原则,将大型复杂系统划分为复杂性较低的较小组件的层次结构。 系统的每个主要组件称为子系统。 面向对象的分解识别系统中的各个自治对象以及这些对象之间的通信。
分解的优点是 −
各个组件的复杂性较低,因此更易于理解和管理。
它可以对具有专业技能的劳动力进行划分。
它允许替换或修改子系统而不影响其他子系统。
识别并发
并发允许多个对象同时接收事件,并且允许多个活动同时执行。 并发性在动态模型中被识别和表示。
为了启用并发,每个并发元素都被分配了一个单独的控制线程。 如果并发是在对象级别,则两个并发对象将被分配两个不同的控制线程。 如果单个对象的两个操作本质上是并发的,那么该对象将被分割到不同的线程中。
并发与数据完整性、死锁和饥饿问题相关。 因此,每当需要并发时就需要制定明确的策略。 另外,并发需要在设计阶段就识别出来,不能留到实现阶段。
识别模式
在设计应用程序时,针对某些类别的问题采用一些普遍接受的解决方案。 这些是设计的模式。 模式可以定义为一组记录在案的构建块,可用于某些类型的应用程序开发问题。
一些常用的设计模式是 −
- 外观图案模式
- 模型视图分离模式
- 观察者模式
- 模型视图控制器模式
- 发布订阅模式
- 代理模式
控制事件
在系统设计过程中,需要识别系统对象中可能发生的事件并进行适当的处理。
事件是对具有时间和空间位置的重大事件的说明。
可以建模的事件有四种类型,即 −
单一事件 − 由一个对象抛出并被另一个对象捕获的命名对象。
通话事件 − 表示操作分派的同步事件。
时间事件 − 代表时间流逝的事件。
更改事件 − 代表状态变化的事件。
处理边界条件
系统设计阶段需要解决整个系统以及每个子系统的初始化和终止问题。 记录的不同方面如下 −
系统的启动,即系统从非初始化状态到稳定状态的转变。
系统终止,即关闭所有正在运行的线程、清理资源以及发送消息。
系统的初始配置以及需要时的系统重新配置。
预见系统故障或意外终止。
边界条件使用边界用例进行建模。
对象设计
开发出子系统的层次结构后,系统中的对象就被识别并设计了它们的细节。 在这里,设计者详细介绍了系统设计过程中选择的策略。 重点从应用领域概念转向计算机概念。 分析期间识别的对象被蚀刻出来以供实施,目的是最大限度地减少执行时间、内存消耗和总体成本。
对象设计包括以下阶段 −
- 对象识别
- 对象表示,即设计模型的构建
- 操作分类
- 算法设计
- 关系设计
- 实施外部交互控制
- 将类和关联打包到模块中
对象识别
对象设计的第一步是对象识别。 在面向对象分析阶段识别的对象被分组并细化,以便它们适合实际实现。
该阶段的功能是 −
识别和细化每个子系统或包中的类
定义类之间的链接和关联
设计类之间的层次关联,即泛化/特化和继承
设计聚合
对象表示
一旦识别了类,就需要使用对象建模技术来表示它们。 此阶段主要涉及构建 UML 图。
需要制作两种类型的设计模型 −
静态模型 − 使用类图和对象图描述系统的静态结构。
动态模型 − 使用交互图和状态图来描述系统的动态结构并显示类之间的交互。
操作分类
在该步骤中,结合OOA阶段开发的三个模型,即对象模型、动态模型和功能模型来定义对对象执行的操作。 操作指定要做什么,而不是如何完成。
执行以下有关操作的任务 −
绘制了系统中每个对象的状态转换图。
针对对象接收的事件定义操作。
识别一个事件触发相同或不同对象中的其他事件的情况。
识别操作中的子操作。
主要操作已扩展到数据流程图。
算法设计
对象中的操作是使用算法定义的。 算法是解决操作中提出的问题的逐步过程。 算法关注的是如何完成它。
可能有不止一种算法对应于给定的操作。 一旦确定了替代算法,就会为给定的问题域选择最佳算法。 选择最优算法的指标是 −
计算复杂度 − 复杂度决定了算法的计算时间和内存需求的效率。
灵活性 − 灵活性决定了所选择的算法是否可以适当地实现,而不损失在各种环境中的适当性。
可理解性 − 这决定了所选算法是否易于理解和实现。
关系设计
需要在对象设计阶段制定实现关系的策略。 解决的主要关系包括关联、聚合和继承。
设计者应该对关联进行以下操作 −
确定关联是单向还是双向。
分析关联路径并在必要时更新它们。
在多对多关系的情况下,将关联实现为不同的对象; 或者在一对一或一对多关系的情况下作为到其他对象的链接。
关于继承,设计者应该做到以下几点 −
调整类及其关联。
识别抽象类。
制定规定,以便在需要时共享行为。
控制的实施
对象设计者可以将细化纳入状态图模型的策略中。 在系统设计中,制定了实现动态模型的基本策略。 在对象设计过程中,该策略被适当地修饰以实现适当的实现。
动态模型的实现方法e −
将状态表示为程序中的一个位置 − 这是传统的过程驱动方法,控制位置定义程序状态。 有限状态机可以作为程序来实现。 转换形成输入语句,主控制路径形成指令序列,分支形成条件,后向路径形成循环或迭代。
状态机引擎 − 这种方法通过状态机引擎类直接表示状态机。 此类通过应用程序提供的一组转换和操作来执行状态机。
控制为并发任务 − 在这种方法中,对象被实现为编程语言或操作系统中的任务。 这里,事件被实现为任务间调用。 它保留了真实对象固有的并发性。
封装类
在任何大型项目中,将实现细致地划分为模块或包非常重要。 在对象设计过程中,类和对象被分组到包中,以便多个组能够在一个项目上协作工作。
封装的不同方面是 −
从外部视图中隐藏内部信息 − 它允许将类视为"黑匣子",并允许更改类的实现,而无需类的任何客户端修改代码。
元素的连贯性 − 如果一个元素(例如类、操作或模块)按照一致的计划进行组织,并且其所有部分本质上相关,以便它们服务于共同的目标,那么它就是连贯的。
物理模块的构建 − 以下指南有助于构建物理模块 −
模块中的类应该表示同一复合对象中类似的事物或组件。
紧密相连的类应该位于同一模块中。
未连接或弱连接的类应放置在单独的模块中。
模块应该具有良好的内聚性,即其组件之间的高度协作。
模块与其他模块应该具有低耦合性,即模块之间的交互或相互依赖应该最小化。
设计优化
分析模型捕获有关系统的逻辑信息,而设计模型则添加细节以支持高效的信息访问。 在设计实施之前,应该对其进行优化,以使实施更加高效。 优化的目的是最小化时间、空间和其他指标方面的成本。
但是,设计优化不应该过度,因为易于实施、可维护性和可扩展性也是重要的考虑因素。 人们经常看到,完美优化的设计效率更高,但可读性和可重用性较差。 所以设计师必须在两者之间取得平衡。
可以为设计优化做的各种事情是 −
- 添加冗余关联
- 省略不可用的关联
- 算法优化
- 保存派生属性以避免重新计算复杂表达式
添加冗余关联
在设计优化期间,会检查派生新关联是否可以降低访问成本。 虽然这些冗余关联可能不会添加任何信息,但它们可能会提高整个模型的效率。
省略不可用的关联
存在太多关联可能会使系统难以解读,从而降低系统的整体效率。 因此,在优化过程中,所有不可用的关联都会被删除。
算法优化
在面向对象的系统中,数据结构和算法的优化是以协作的方式完成的。 一旦类设计到位,就需要优化操作和算法。
算法的优化通过以下方式获得 −
- 重新排列计算任务的顺序
- 与功能模型中规定的循环执行顺序相反
- 删除算法内的死路径
派生属性的保存和存储
派生属性是指其值作为其他属性(基本属性)的函数进行计算的属性。 每次需要时重新计算派生属性的值是一个耗时的过程。 为了避免这种情况,可以计算这些值并以其计算形式存储。
但是,这可能会导致更新异常,即基本属性值发生变化,而派生属性值没有相应变化。 为了避免这种情况,采取以下步骤 −
每次更新基本属性值时,也会重新计算派生属性。
所有派生属性都会在组中定期重新计算和更新,而不是在每次更新后进行。
设计文档
文档是任何软件开发过程的重要组成部分,它记录了软件的制作过程。 任何重要的软件系统都需要记录设计决策,以便将设计传输给其他人。
使用范围
虽然是次要产品,但良好的文档是必不可少的,特别是在以下领域 −
- 设计由多个开发人员开发的软件
- 迭代软件开发策略
- 开发软件项目的后续版本
- 用于评估软件
- 用于查找测试条件和领域
- 用于软件维护。
内容
有益的文档主要应包括以下内容 −
高层系统架构 − 流程图和模块图
关键抽象和机制 − 类图和对象图。
说明主要方面行为的场景 − 行为图
功能
优秀文档的特点是 −
简洁、明确、一致且完整
可追溯至系统的需求规范
结构良好
图解而非描述