使用GRASP的对象设计示例

目标:

  • 设计用例实现
  • 应用GRASP为类分配职责。
  • 应用UML阐述和思考对象的设计

本章对案例研究应用了OO设计原则和UML,并展示了大量示例用以阐述如何合理地设计对象的职责和协作。请注意,GRASP模式的名称本身并不重要,它只是帮助我们系统地思考基本OO设计的辅助手段。

本章将详细阐述OO开发者如何基于设计原则进行思考。实际上,经过一段时间的实践后,这些原则在头脑中将变得根深蒂固,并且有时交互会下意识地做出决断。

但是首先,在对象设计中不需要什么“魔力”,不需要任何不合道理的决断-职责的分配和协作的厦时能够被合理解释和学习的。事实上,OO设计更近于科学而非艺术,尽管存在巨大的创造性和优雅设计的空间。

什么时用例实现

上一章的基本OO设计原则侧重于细微的设计问题。相比之下,本章对领域对象设计的描述着眼于整个用例场景。你将看到更为大型的协作和更为复杂的UML图。

用例实现描述某个用例基于协作对象如何在设计模型中实现。更精确的说,设计者能够描述用例的一个或多个场景的设计,其中的每个设计都称为用例实现。用例实现时UP术语,用以提示我们在表示为用例的需求和满足需求的对象之间的联系。

UML图是描述用例实现的常用语言。我们可以在此用例实现的设计工作中应用对象设计的原则和模式。

制品之间的一些关系:

  • 用例指出了SSD中所示的系统操作。
  • 系统操作可以成为输入到领域层交互图的控制器中的起始消息。
    • 那些OOA/D的初学者经常忽略这个关键点。
  • 领域层交互图阐述了对象如何交互已完成所需任务-用例实现。

制品注释

SSD、系统操作、交互图和用例实现

在NextGen POS的当前迭代中,我们要考虑处理销售用例的SSD中所确定的系统操作和场景:

  • makeNewSale
  • enterItem
  • endSale
  • makePayment

如果我们使用通信图来描述用力实现,我们将绘制不同的通信图来表示对每个系统操作消息的处理。当然对于顺序图也是同样如此。

用例和用例实现

很自然,用例是用例实现的首要输入。用例文本和补充规格说明、词汇表、UI原型、报表原型等所表述的相关需求向开发者提供需要构建的全部内容。但是要时刻记住,所记录的需求是不完善的-通常是相当不完善。

操作契约和用例实现

如前所述,可以直接根据用例文本或某人的领域只是来设计用例实现。但是对于某些复杂的系统操作,就需要编写契约以获得更多的分析细节。

对于每一个契约,我们会依据对相关用力文本的思考,完成后置条件的状态变更,并设计消息的交互以满足需求。

领域模型和用例实现

在交互图中,领域模型指出了需要设计软件对象。和所欲分析制品一样,现有的领域模型也不是完美的,你应该想到其中会哟u错误和遗漏。你将发现以前遗漏的新概念,也会排除以前所定义的一些概念,对关联和属性亦是如此。

设计模型中的设计类及其名称是否必须来源于领域模型?完全不是,在设计工作中通常会发现一些在早期领域分析中所遗漏的新概念,同时也经常会虚构一些软件类,这些软件类的名称和目的可能会与领域模型完全无关。

下一步工作

NextGen迭代的用例实现

接下来将基于GRASP模式揭示采用对象对用例实现进行设计过程中所作出的选择和决策。储中将有意识地进行详细揭示,以表明在OO设计中并不存在什么魔力--一切将基于合理的原则。

初始和“启动”用例

启动用例实现是设计语境,在该语境中要考虑创建大部分的“根”或生命期长的对象。

在编码时,至少首先要编写启动初始化的出现。但是在OO设计建模的过程中,要最后考虑启动初始化,直到发现那些时真正的需要被创建的和初始化的。然后,在对初始化进行设计以支持其他用例现实的需要。

基于这一准则,我们将在设计启动之前探讨处理销售用例实现。

如何设计makeNewSale

顾客携带所购物品到达收款台后,收银员发出请求以启动一次新的销售交易,此时将发生makeNewSale系统操作。该用例足以决定什么时需要的,但是为了在案例研究中解释这一方法,我们为所有系统操作编写了契约。

契约CO1:makeNewSale

  • 操作:amkeNewSale()
  • 交叉引用:用例:处理销售
  • 前置条件:无
  • 后置条件:
    • 创建了Sale的实例s(创建实例)。
    • s被关联到Register(形成关联)。
    • s的属性被初始化(修改属性)。

选择控制器类

我们首先要做出的决策时为系统操作消息enterItem选择控制器类。根据控制器模式,这里由一些选择

| 表示整个“系统”、“根对象”,特定设备或主要子系统 | Stroe:一种根对象,因为我们认为大部分其他领域对象在Store之内
Register:运行软件的特定设备,叶恒为POSTerminal
POSSystem:整个系统的名称 | | 表示用例场景中所有系统事件的接收者或处理者 | ProcessSaleHandler:其构成模式时:<用例名称>“Handler”或“Session”
ProcessSaleSession |

如果只有为数不多的系统操作,并且外观控制器并不承担太多的职责,则选择Register这样的设备对象外观控制器时适宜的。当我们由大量系统操作并且希望分布职责以使每个控制器类都是清理的转账的,此时应该选择用例控制。在本案例中,由于只存在少量的系统操作,因此Register就可以满足要求。

记住,此Register使设计模型中的软件对象,而不是物理中断。

创建新的Sale

我们必须创建软件对象Sale,GRASP的创建者模式建议给某个类赋予创建职责,使其能够聚集、容纳或记录要创建的对象。

分析一下领域模型就会发现,可以认为Register使记录Sale的类。确实,在业务中, “Register”一次以及使用了数百年了,其含义正式记录账户交易的事物。

因此Register使创建Sale的合理候选者。注意其中使如何降低表示差异的。通过让Register创建Sale,我们能够方便地将Register与Sale关联起来,这样在会话的未来操作期间,Register将引用有当前的Sale实例。

除了上述内容之外,当创建Sale时,还必须创建一个空集合来记录所有将来会添加的SalesLineItem实例。该集合包含在Sale实例中,并由Sale维护,这意味着就创建者模式而言,Sale时创建该集合的合理候选。

所以,Register创建Sale,而Sale创建空集合,这些都可以由交互图中的多个对象来表示。

总结

这个设计并不困难,但是对控制器和创建者进行详细解释主要时为了阐述如何根据原则和模式合理、系统地决定和解释设计的细节。

如何设计enterItem

在收银员输入顾客所买物品的itemID及熟练时,将发生enterItem系统操作。一下时完整的契约

契约CO2:enterItem

  • 操作: enterItem(itemID:ItemID, quantity:integer)
  • 交叉引用:用例:处理销售
  • 前置条件:正在进行销售
  • 后置条件:
    • 创建了SalesLineItem的实例sli(创建实例)。
    • sli被关联到当前Sale(形成关联)。
    • sli.quantity赋值为quantity(修改属性)。
    • 基于itemID的匹配,sli被关联到ProductDescription(形成关联)。

我们选择将构造满足enterItem后置条件的交互图,同时使用GRASP模式以做出设计决策。

选择控制器类

继续使用Register作为控制器。

是否要现实商品项目的描述和价格

由于模型-视图分离的原则,非GUI对象的职责不应设计输出任务。因此,尽管在用例中生命该操作之后要现实描述和价格,但是哦我们此时将忽略现实的设计。

关于对信息现实职责中所有需要被现实的信息时已知的,因此这里的设计要包含这些信息。

创建新的SalesLineItem

enterItem契约的后置条件表明需要创建初始化以及建立于SlesLineItem的关联。分析领域模型后会发现一个Sale包含多个SalesLineItem对象。收领域模型的启发,我们决定软件的Sale也同样可以包含多个软件的SaleLineItem。所以,根据创建者模式,Sale的软件对象时创建SalesLineItem对象的何时候选者。

寻找ProductDescription

SalesLineItem要于匹配进入的itemID的ProductDescription建立关联。这意味着基于一个itemID匹配,必须返回一个ProductDescription。

在考虑如何实现此查询功能之前,应先考虑由谁承担此职责。所以,第一步是,通过清晰陈述职责来开始分配职责。

ProductCatalog的可见性

谁应该发送getproductDescription消息给ProductCatalog来请求Product Description呢?

如果某对象要发送消息到另外一个对象时,它必须拥有对接受消息对象的可见性。

最终设计

如何设计endSale

在收银员按下表示商品条目输入完毕的按钮后,将会产生endSale系统操作。

契约CO3:endSale

  • 操作:endSale()
  • 交叉引用:用例:处理销售
  • 前置条件:正在进行的销售
  • 后置条件:
    • Sale.isComplete被置为真(修改属性)。

选择控制器类

继续使用Register作为控制器

设置Sale.isComplete属性

计算销售总额

设计Sale.getTotal

如何设计makePayment

当收银员输入所支付的现金数额后,将发生makePayment系统操作。以下时该操作的完整契约: 契约CO4:makePayment

  • 操作:makePayment(amount:Money)
  • 交叉引用:用例:处理销售
  • 前置条件:有正在进行销售
  • 骤中条件:
    • 创建了Payment的实例p(创建实例)
    • p.amountTendered被赋值为amount(修改属性)。
    • p被关联到当前的Sale(形成关联)。
    • 当前的Sale被关联到Store(形成关联)。

创建Payment

当存在戈多可选设计时,应更深入地观察可选设计所存在的内聚和耦合,以及未来可能存在的进化压力。选择具有良好内聚、耦合和在未来出现变化时能保持稳定的设计。

记录Sale的日志

计算余额

NextGen迭代1最终的DCD

如何将UI层连接到领域层

UI层对象获取到领域层对象可见性的常见设计如下:

  • 从应用的启示方法中调用的初始化对象,同时传教UI对象和领域对象,并且领域对象传递给UI
  • UI对象从众所周知的源提取领域对象。

初始化和“启动”用例

何时创建初始化的设计

大多数系统都具有隐式或显式的启动用例,并且具有与应用启动相关的初始化系统操作。尽管startUp系统操作时最早要执行的操作,但是在设计设计中要将该操作交互图的开发推出到其他所有系统操作的设计工作之后。这一实践保证能够发现所有相关初始化活动所需的信息,这些活动将用于支持其后的系统操作交互图。

最后完成初始化的设计

如何完成应用的启动

启动用例中的startUp系统操作抽象地表示应用开始执行的初始化阶段。为理解如何为这一操作设计交互图,首先必须要理解会发生初始化的语境。应用如何启动和初始化与编程语言和操作系统有关。

对于所有的情形,创建的设计约定时创建一个初始化领域对象或一组对等额初始领域对象,这些对象时首先要传教的软件“领域”对象。这些创建活动可以显示地在最初的mian方法中完成,也可以从main方法调用Factor对象来完成。

通常,一旦创建了初始领域对象,该对象将复杂传教其直接的子领域对象。

选择初始领域对象

初始领域对象的类应该时什么

选择位于或接近于领域对象包含或聚合层次中的跟类多为初始化领域对象。该类可能时外观控制器,也可能时容纳所有胡哦大部分其他对象的某些对象。

对高内聚和低耦合的考虑将影响对这些候选者的选择。在本应用中,我们选择Store作为初始对象。

设计Store.create

传教和初始化的任务来一组以前设计工作的需要。

领域模型和设计模型中对象类之间的多重性可能有所不同。

Monopoly迭代的用例实现

过程:迭代和进化式对象设计

在前面几章里,我为用例实现的迭代和进化式对象设计提出了大量建议。其中的基本观点时:保持设计的轻量化和简短,快速将纳入编码和测试,不要视图在UML模型中细化所有事物。对设计中有创造性和款那的不放呢进行建模。

UP中的对象设计

再次以UP作为迭代方法的示例:用例实现时UP设计模型的一部分。

  • 初始:不在细化阶段,通常不会开始设计模型和用i是西安的设计。
  • 细化:在该阶段中,可能要为设计中大部分在架构上重要的胡哦有风险常见创建用例实现。然而,不会为所有常见绘制UML图形,并且不需要完整和细粒度的细节。其思想时着重于主要的设计决策,为关键用例实现绘制交互图,这将受益于对可选方案的深思熟虑。
  • 构造:为当前存在设计问题构造用例实现。

总结

设计对象交互和职责分配时对象设计的核心。这些设计决策对对象软件系统是否清晰、是否具有扩展性和可维护性具有重大影响,同时也对构建服用的程度和质量具有影响。职责分配可以遵循一定的原则,GRASP模式总结了面向对象设计常用的原则。

results matching ""

    No results matching ""