UML和模式应用读书笔记三(GRASP)

GRASP

Creator

十分有趣!这反映了一种"直觉",OO软件开发者常常想让容器创建被容纳的事物,就像Board创建Square一样

建议
如果以下条件之一为真时(越多越好),将创建类A实例的职责分配给类B:

  • B包含或组成聚集了A
  • B记录A
  • B紧密地使用A
  • B具有A的初始化数据

上面的说法体现了这样一种暗示:没有标准答案,因为可能有好几个候选答案,只是选择了暂时占数量优势的那个.特别是业务逻辑发生变动后,第一名可能变化,这个时候就要修改创建者么?
另外结合restful而言,这是否需要将创建A的操作POST到B的URL呢?
上面有些标准几乎是废话,而有些不知所云,例如B记录A,为什么A不能记录自己,B是记录A的数量信息么?另外我一般采用的标准是谁的信息能决定A是否创建成功难么就由谁来创建A
另外直接创建A看上去也是一个公平的方案,特别是对于创建A有好几个候选者而言.
另外需要注意的问题是B创建了A之后对A的操作会影响B么?还是直接修改B?也就是说A是否是B的一部分

组合聚集部分,容器容纳内容,记录者进行记录,所有这些都是类图中类之间极为常见的关系.创建者模式建议,封装的容器或记录器类是创建其所容纳或记录的事物的很好的候选者.

这里其实是认知上的回归,在我看来,容器的目的(例如合同)就是为了容纳内容(例如条款),是先有了内容,再有容器来容纳,容器就是为了这个目的而生的.只是后来在操作顺序上发生了改变.

对象的创建常常具有相当的复杂性…在这些情况下,最好的方法是把创建职责委派给称为具体工厂或抽象工厂的辅助类,而不是使用创建者模式所建议的类

Information Expert

建议
把职责分配给具有完成该职责所需信息的那个类
当有多个局部信息专家时,将职责赋予具有支配作用的信息专家,即持有主要信息的对象.这样有助于支持低耦合
当存在多个选择时,考虑每个选择对耦合和内聚的影响,由此选择最佳方案
当基于其他准则还是无法明确地选择时,考虑未来演化方向

上面第二条通俗的来讲就是谁持有的信息多,就用它
上面第三条通俗的来讲就是谁的责任少,就分给他
上面第四条也是废话,不过这触发了我的灵感,就是可以先把这个职责放一放,等其他职责明确了之后再使用上面方案

有时,可以通过寻找具有初始化数据的类来确定创建者,这些数据将在创建过程中传递给被创建者.这实际上就是专家模式的例子

在某些情况下,专家模式建议的方案也许并不合适,通常这是由于耦合和内聚问题所产生的
例如,谁应当负责把Sale存入数据库呢?的确,大多数要保存的信息位于Sale对象中,于是专家会建议将此职责分配给Sale类…因此,Sale类不仅仅关注"作为销售"的纯应用逻辑.现在由于存在其他职责而降低了它的内聚

这里对于信息可以进行多重理解,一种是按照种类理解,在存储中多个attribute只能算为一类,多个行也算一类,然后数据库的连接信息算一类,这样进行加权处理后Sale不再是存储时的专家了.另一个更勉强的看法是把attribute看做多个信息,但是同样的多个column(因为列名和attribute可以不一样)也看做多个信息,再考虑数据库连接信息,Sale的信息不再充足.当然,这两种观点都牵强了点

持非是Controller或Create,Information Expert是我们应首先考虑应用的模式

可见性,文中举了一个例子讲到当一个操作需要两个Expert时把这个操作给到哪个Expert,其依据原则是可见性.具体分析如下

其职责陈述如下:
谁负责获知支付余额呢?
为了计算支付余额,需要知道销售总额和所收取的支付现金总额.因此,Sale和Payment是解决这个问题的部分信息专家.
如果主要由Payment负责获知余额,那么他需要具有Sale的可见性,以便向Sale请求销售总额.由于到目前为止Sale对于Payment来说还不可见,因此会增加整体设计的耦合度–不支持低耦合模式
相反,如果主要由Sale负责获知余额,他需要具有Payment的可见性,以便向Payment请求所收取的现金总额.由于Sale是Payment的创建者,Sale已经拥有对Payment的可见性,因此该方法不会增加设计的耦合度,因此这是较好的设计

面对这个问题,我的一点个人看法.如果Payment必须依赖于一个Sale的情况下,何况还必须由Sale来创建的情况下,考虑把Payment作为Sale的内部类,这样的情况下可见性是反过来的,即被创建者可见创建者.另一个视角是考虑一对多的情况,结论是在的一方

Low Coupling

建议
分配职责以使耦合保持在较低的水平.

上面几乎是一句废话

因为低耦合往往能够减少修改软件所需时间,工作量和缺陷

在C++,Java和C#这样的面向对象语言中,从TypeX到TypeY耦合的常见形式包括:

  • TypeX具有应用TypeY的实例或TypeY自身的属性
  • TypeX对象调用TypeY对象的服务
  • TypeX具有以任何形式引用TypeY的实例或TypeY自身的办法.通常包括类型TypeY的参数或局部变量,或者由消息返回的对象是TypeY的实例
  • TypeX是TypeY的直接或间接子类
  • TypeY是接口,而TypeX是此接口的实现

其中子类可否用子集来理解

低耦合的极端例子是类之间没有耦合.这个例子违反了对象技术的核心隐喻:系统由相互连接的对象构成,对象之间通过消息通信.

高耦合对于稳定和普遍使用的元素而言并不是问题.例如J2EE应用能安全地将自己与java.util耦合,因为Java库是稳定,普遍使用的
高耦合本身并不是问题所在,问题是与某些方面不稳定的元素之间的高耦合,这些方面包括接口,实现等.

作为设计者…你必须在降低耦合和封装事物之间进行选择,应该关注在实际当中极其不稳定或需要改进的地方

优点

  • 不收其他构件变化的影响
  • 易于单独理解
  • 便于复用

Controller

在这里插入图片描述
这个原则是用于回答上面的的???处应该是什么.

名称:控制器
问题:在UI层之上首先接收和协调系统操作的对象是什么?
建议
把职责分配给能代表下列选择之一的

  • 代表全部系统,根对象,运行软件的设备或主要子系统
  • 代表发生系统操作的用例场景

我的一般做法是在???处放置UI层上对应呈现信息的domain,这样前后端是一致的.
控制器又可以分为外观控制器用例控制器两类,前者代理了几乎所有操作,而后者是各种用例的Handler
在这种模式下,所有对domain的调用都要通过控制器,即使是富客户端的内部调用,也就是说除了控制器,其他地方都不允许调用领域层.所以复用的单位是控制器,他们代理了domain的各种操作

High Cohesion

建议
职责分离应保持高内聚,以此来评估备选方案

这是另一句废话
个人思考:高内聚的两种思考方式:一个操作尽可能影响多的属性一个属性尽可能受多的操作的影响,这是依从内部观察的视角,从外部来看,当调用一个对象时尽可能调用其多的方法,
提高内聚的一种方式是如下图
在这里插入图片描述
在这里插入图片描述
这点从应用的类也可以知道,另一方面

var a = createA();
b.add(a)

如果上面的两行代码总是同时出现(这是个现象),探究其背后的原因后(这是业务上的诉求)可以改成下面代码

b.createA()

class B{
	public void createA(){
		createA();
		//...
	}
}

关于内聚书中举了一个很好的例子来说明要避免现实世界中(特别是关系型数据库)的影响,遗憾的时我很久才参悟到这个道理

假设存在称为Company的类,他负责了解所有雇员信息和财务信息的工作.这两个领域虽然在逻辑上都与公司概念相关,但是彼此之间并没有太多关联.此外,其公共的方法并不多,其代码总量也不多.

反例

有两种情况可以违反高内聚的原则:

  1. 由于开发人员的技能不同(并且短时间无法通过学习来精通)从而导致每个开发人员维护自己技术域的工作,书中的一个例子是OO程序员和SQL专家各自维护.这是一种向人员妥协的情况
  2. 另一种是向性能妥协的情况(不过在我看来恰当的低耦合高内聚应该是有利于性能解决的)在调用远程方法时尽肯能一次调用代替多次调用,文中举的例子是使用setData来代替setName,setSalarysetHireDate,但我觉得这不是一个好的例子.我倒是觉得在写操作中比较少遇到(除非页面上就体现出两个操作被合并了),倒是查询时,特别是多个下拉框的查询也许会遇到

Pure Fabrication

面向对象设计有时会被描述为:实现软件类,使其表示真实世界问题领域的概念,以降低表示差异;然而,在很多情况下,只对领域层对象分配职责会导致不良内聚或耦合,或者降低复用潜力.

第一个例子给我的感觉是纯虚构都是会产生一些非业务的技术类,但是第二个例子给出了不一样的答案,这里完全遵循了高内聚和复用的设计思想.

此类对象的设计可以广泛地分为两组
通过representational decomposition所表示的选择
通过behavioral decomposition所产生的选择

诸如Sale等软件类的创建是根据表示解析得来的,这种软件类涉及或代表领域中的事物.表示解析是对象设计中的常见策略,并支持低表示差异的目标.但是有时,我们需要通过对行为分组或通过算法来分配职责,而无需创建任何名称或目的与现实世界领域概念相关的类.
禁忌那些对象设计初学者和更熟悉以功能组织和分解软件的人有时会滥用行为解析及纯虚构对象.夸张的是,功能正好变成了对象…如果滥用纯虚构,会导致大量行为对象,其职责与执行职责所需的信息没有结合起来,这样会对耦合产生不良影响.其通常征兆是,对象内的大部分数据被传递给其他对象用以处理.

Indirection

问题为了避免两个或多个事物之间直接耦合,应该如何分配职责?如何使对象解耦合,以支持低耦合并提高复用性潜力?
解决方案将职责分配给中介对象,使其作为其他构件或服务之间的媒介,以避免他们之间的直接耦合.中介实现了其他构件之间的间接性

计算机科学中大多数问题都可以通过增加一层间接性来解决.大多数性能问题都可以通过去除一层间接性来解决.

Polymorphism

Protected Variations

问题如何设计对象,子系统和系统,使其内部的变化或不稳定不会对其他元素产生不良影响?
解决方案识别预计变化或不稳定之处,分配职责用以在这些变化之外创建稳定接口

不要和陌生人讲话

不要历经远距离的对象结构路径去向远距离的间接对象发送消息.这种设计对于对象结构中的变化而言极为脆弱.

只应该给以下对象发送消息

  1. this
  2. 方法参数
  3. this属性
  4. 作为this属性的集合中的元素
  5. 在方法中创建的对象

并不总是需要对此进行防范,这种防范依赖于对象结构的不稳定性.在标准库中,对象类之间的结构连接是相对稳定的.

预测PV和选择你的战斗

首先,值得定义以下两个变更点

  • 变化点:现有,当前系统或需求中的变化
  • 进化点:预测将来可能产生的变化点,但并不存在于现在需求中

有时…也就是说,对进化点的预防性工程成本要高于对简单设计重做的成本

初始化和启动用例

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

准则
最后完成初始化的设计

可见性

  • 全局可见
    B是全局变量
  • 属性可见
    B是A的属性
  • 参数可见
    B是A方法的参数
  • 局部可见
    B是A方法的变量

对于后两种有的时候会把方法可见性转换为属性可见性,有的时候反过来,通过调用一个get方法来获取属性可见性(一般是List或Map)里的一个元素,这个时候,元素是局部可见
这里附带一张图体现了从Controller到Domain中id编程了对象
在这里插入图片描述

改进耦合

当对象A持续需要对象B中的数据时,意味着

  1. 对象A不应该持有该数据
  2. 对象B而不是对象A应该具有这一职责(基于专家模式)

因为Player与其所处于的Square具有持续的协作,所以是Player而不是Piece永远需要知道自己所在的Square位置.
对此设计进行了精化,将知道其所处方格的职责分配给Player而不是Piece…实际上,我们甚至可以怀疑Piece对象在领域模型中是否有用.

有时,开发者会针对某些未知的可能性变化进行"未来验证"的推测,由此而使用接口和多态来设计系统…但是,这里需要有临界评价,因为有些时候使用多态设计形成的变化点在实际中不一定发生或从未设计发生过,这种不必要的付出很常见.在投入灵活性的改进前,要现实的对待可变性的真实可能性.

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值