GRASP模式学习心得

文章目录

GRASP模式学习心得

本文分三个层次一、GRASP—“通用职责分配软件模式”二、九个模式的概念、提出的问题以及解决方案三、在实际编程中的应用# 系列文章目录参考文献

本文分三个层次

GRASP是学习使用设计模式的基础

一、GRASP—“通用职责分配软件模式”

1、概念 GRASP,全称为General Responsibility Assignment Software Pattern,即通用职责分配软件模式。GRASP模式描述了对象设计和责任分配的基本原则和模式。 确定需求并创建领域模型后,如何将方法添加到Class类中,并定义对象之间的消息传递以满足要求。GRASP是学习使用设计模式的基础,是一种学习辅助工具,可帮助人们理解基本对象设计,并以有条理,合理,可解释的方式应用设计推理。这种理解和使用设计原则的方法基于分配责任的模式。 2、核心思想 它是站在面向对象设计的角度,告诉我们怎样设计问题空间中的类与分配它们的行为职责,以及明确类之间的相互关系等,而不像GoF模式一样是针对特定问题而提出的解决方案。因此GRASP站在一个更高的角度来看待面向对象软件的设计,它是GoF设计模式的基础。GRASP是对象职责分配的基本原则,其核心思想是**职责分配(Responsibility Assignment),用职责设计对象(Designing Objects with Responsibilities)。 3、有九个模式:信息专家、创建者、高内聚、低耦合、控制器、多态性、纯虚构、间接性、防止变异。(下面对九个模式具体讲解)

二、九个模式的概念、提出的问题以及解决方案

1、五个基本模式:信息专家、创建者、高内聚、低耦合、控制器;

2、四个扩展模式:多态性、纯虚构、间接性、防止变异。 (1)信息专家 “信息”不单指数据。 问题:给对象分配职责的基本原则是什么? 解决方案:把职责分配给信息专家,它具有实现这个职责所必需的信息 优点: • 对象使用自身信息来完成任务,所以信息的封装性得以维持,因此支持了低耦合(至少不会增加耦合性)。 • 行为分布在那些具有所需信息的类之间,这样功能更集中,因此支持了高内聚。 相关模式或原则: • GRASP:低耦合、高内聚

注意:和“关注点分离”一起使用使得对象进一步内聚,从而达到高内聚,也能降低耦合。 举例:获取所有买的商品总金额,Order和Goods是一对多的关系。

分析:Order本身关联了Goods,并且理解Goods的结构。在图例中Client通过Order获取了Goods并做了逻辑运算得出商品总金额,这种做法产生了不必要的依赖增加了耦合数量,商品总金额计算的职责由Order承担最合适。

延伸:在某些情况下,该方案并不合适,通常是由于耦合与内聚问题产生的,如:谁应该把对象A存入数据库?按照原则每个类都应该具有把自己持久化的能力。

(2)创建者 创建者指导我们分配那些与创建对象有关的职责。如此选择是为了保持低耦合。 问题:谁应该负责创建某类的新实例? 解决方案:满足以下条件之一时,将创建类A的职责分配给类B(当满足1条以上时,通常首选包含或聚合)。 • B“包含”或聚合A。 • B记录A。 • B频繁使用A。 • B具有A的初始化数据,该数据将在创建时传递给A。

优点:支持低耦合,因为创建者和被创建者已经存在关联,所以这种方式不会增加耦合性。 相关模式或原则: • GRASP:低耦合 • GoF:具体工厂、抽象工厂 • 其他:整体-部分

注:包含(作者在这里标注了“”,因为包含在uml是表达用例关系的,用来说明对象关系也可以)、聚合、整体-部分 看UML定义;包含强调了强依赖(A是B的子集,A属于B,缺少了A时B不是整体),聚合是弱依赖(B由A组成,A不属于B)。 例子:

• Order包含Goods(Order脱离Goods就失去了完整性,没有存在的意义)。 • Order记录相关的Goods。 • Goods初始化数据: o 情况一:只需要订单上的Goods数据,这种情况Order具有Goods的初始化数据。 o 情况二:订单上的Goods数据不完整,这种情况Order只有Goods初始化数据的一小部分,Order不能做为创建者。 (3)高内聚、低耦合 耦合是对某元素与其他元素之间的连接、感知和依赖程度的度量,内聚是对元素职责的相关性和集中度的度量(这里的元素指类、系统、子系统等等),耦合和内聚是从不同角度看待问题,他们互相依赖的互相影响的(以下两点也可以反过来说): • 内聚过低,相关功能分散在不同模块中,需要增加额外的耦合使这些功能聚合在一起,发生变更时影响多个模块。

• 内聚过高,不相关的功能聚集在一个模块中,耦合度高,发生变更时会产生意想不到的影响。 **高内聚 内聚是对元素职责的相关性和集中度的度量。 问题:怎么样保持对象是有重点的、可理解的、可维护的,并且能够支持低耦合? 解决方案:按照相关性分配职责,可保持较高的内聚。 优点: • 分解后的元素更加简单易于理解和维护。 • 按照相关性拆分可以提高重用性。 相关原则和模式:单一职责原则、关注点分离、模块化。

**低耦合 耦合是对某元素与其他元素之间的连接、感知和依赖程度的度量。这里的元素指类、系统、子系统等等。 问题:怎样降低依赖性,减少变化带来的影响,提高重用性? 解决方案:分配职责,使耦合尽可能低。利用这一原则评估可选方案。 相关模式或原则: 通过结构化管理来保持低耦合、高内聚。 低耦合与高内聚小结:通过结构化管理来保持低耦合、高内聚。 (4)控制器 解决方案:把职责分配给能代表以下选择之一的类: • 代表整个“系统”、“根对象”、运行软件的设备或主要子系统,这些是外观控制器的所有变体。 • 代表用例场景,在该场景中发生系统事件。 相关模式: • GRASP:纯虚构 • GoF:命令、外观 • 其他:层 控制器的核心是提供一个统一入口,避免客户对元素内部进行耦合,很好的维护了边界: • api层 • 根对象 • 接口

(5)多态性 问题:如何处理给予类型的选择?如何创建可插拔的软件构件? 解决方案:当相关选择或行为随类型有所不同时,使用多态操作为变化的行为类型分配职责。 优点:可扩展性强,同时不影响客户。 相关原则和模式: • GRASP:防止变异 • GoF:大量模式

举例:订单退款时需要计算出用户退款金额和商户扣款金额,在没有新零售业务进来之前直接使用计算服务返回的数据结构,新零售进来后数据结构未统一,需要进行适配,实现多态后的代码扩展性很强。

在微服务架构中,比较复杂的多态问题通常会选择增加一层去解决,如:支付网关、交付网关。

(6)纯虚构 为了保持良好的耦合和内聚,捏造业务上不存在的对象来承担职责。 问题:当你并不想违背高内聚和低耦合或者其他目标,但是基于专家模式所提供的方案又不合适时,哪些对象应该承担这一职责? 解决方案:对人为制造的类分配一组高内聚的职责,该类并不代表问题领域的概念–虚构的事物,用以支持高内聚、低耦合和复用。 优点: • 支持高内聚,因为职责被解析为细粒度的类,这种类只着重于极为特定的一组相关任务。 • 增加了潜在的复用性。

相关原则和模式: • GRASP:低耦合、高内聚。 • 通常接纳本来是基于专家模式所分配给领域类的职责。 • 所有GoF设计模式都是纯虚构,事实上所有其他设计模式也都是纯虚构。

举例:计算商品总数量。根据专家模式计算商品总数量的职责也应该是分配给Order,照这样分配下去商品相关的还会有:总重量、总体积、总XX,这时Order的职责就会越来越多也可能会产生额外的耦合,通过纯虚构对象把这些职责分配出去能够得到更好的设计。

通过虚构对象GoodsItems承担和商品聚合计算相关的职责。 延伸:经常发现代码中会使用Util、Handler、Service这样的虚构类,缺点是这些类通常是单例并共用的,这些虚构类的职责会越来越多(一个Util类2000行代码),创建和业务更相近的虚构对象才能便于理解和管理耦合关系。

(7)间接性

计算机学科中的大多数问题都可以通过增加一层解决,如果不行再加一层。反过来大多数性能问题都可以通过去掉一层来解决。 问题:为了避免两个或多个事物之间直接耦合,应该如何分配职责? 解决方案:将职责分配给中介对象,使其作为其他构建或服务之间的媒介,以避免他们之间的直接耦合。 优点:实现了构件之间的低耦合。 相关原则和模式:

• GRASP:防止变异、低耦合、大量间接性中介都是纯虚构 • GoF:大量模式

注意:间接性通常用来支持防止变异。

(8)防止变异 该模式基本等同于信息隐藏和开闭原则。如何做到在不修改原来功能的前提下对变化的部分进行扩展?识别不稳定因素是特别困难的,也决定了我们能否做出符合开闭原则的设计。 问题:如何设计对象、子系统和系统,使其内部的变化或不稳定性不会对其他元素产生不良影响。 解决方案:识别预计变化或不稳定之处,分配职责用以在这些变化之外创建稳定接口。 相关原则和模式: • GRASP:间接性、多态 • GoF:大量模式 • 其他:接口、数据封装

三、在实际编程中的应用# 系列文章目录

案例1:Jpa替换为Mybatis

@Component

public class CloseOrderService {

@Autowired(required = false)

@Qualifier("rstOrderTransactionManager")

JpaTransactionManager tm;

public void invalid_order(Long orderId, Long userId, Short processGroup)

throws UserException, SystemException, UnknownException {

//其他逻辑。。。省略

// 开启事务

DefaultTransactionDefinition def = new DefaultTransactionDefinition();

TransactionStatus ts = tm.getTransaction(def);

try {

order = orderDAO.get(orderId);

order.setStatusCode(toStatus);

order.setUpdatedAt(new Timestamp(System.currentTimeMillis()));

orderDAO.save(order);

//提交事务

tm.commit(ts);

} catch (Exception e) {

if (!ts.isCompleted()) {

//回滚

tm.rollback(ts);

}

if (e instanceof SatisfiedStateException) {

return;

}

throw e;

}

}

@Transactional(transactionManager = "rstOrderTransactionManager", rollbackFor = Exception.class)

public void invalidOrder(){

}

}

@Component

public interface OrderDAO extends JpaRepository {

@Query(value = "sql语句", nativeQuery = true)

Long generateGlobalOrderId(@Param("userId") Long userId,

@Param("restaurantId") Long restaurantId,

@Param("seqName") String seqName);

}

变化带来的影响:如果不出意外对Jpa的使用方式不会产生变更,意味着其相对稳定,所以在当前阶段来看以上耦合是正常的也不会产生负面影响。但是在以下场景会让我们对高耦合有很明显的体感:大家觉得Jpa不好用,想替换为Mybatis该怎么做?代码中直接使用了继承JpaRepository的OrderDAO做数据操作,由于Jpa和Mybatis的写法不同,所以需要把使用到OrderDAO的地方都做替换:

• 调用OrderDAO的类(70多个类)都需要替换为新的dao。 • 使用JpaTransactionManager.getTransaction()的位置需要替换为MyBatis的TransactionManager。 • @Transactional(transactionManager=“rstOrderTransactionManager”)的位置需要改为编写事务提交和回滚的代码块儿,便于做灰度。 • 以上改动的位置需要增加开关做灰度。

结论:由于变更涉及到70多个类,同时事务管理器获取方式也需要修改,其带来的影响还是挺大的,不满足“低耦合”原则,可以使用“多态”原则重新设计。 案例2:订单对应的支付单应该由谁来创建? 拿饿了么交易系统举例,当前创建支付单的职责是由bos服务承担(面向app的一个后端服务)的,接下我们进行分析。

支付单创建分为两种场景:

• 创建订单和支付单是在一次操作中完成。 • 用户回到订单列表页点击“去支付”时创建支付单。

支付单创建依赖:

• 订单号 • 支付金额 • 支付类型 • 一堆支付系统分配的用于识别业务的参数

注1:如果饿了么只会有外卖一种交易业务,当前的设计还是很稳定的,不会出现太大变化。所以识别变化点才能更好的评判当前系统设计是否合理,如:饿了么将升级为本地生活服务公司,根据公司战略多少能看出我们将来不只外卖业务存在,还会有很多和本地生活相关的交易业务,这些业务会有自己的展示层(app、h5、web)同时对应会有类似bos的服务,如果有10个业务方,在支付场景就需要去对接10次,而由order做就只需要一次(支付作为工具已经比较稳定,不会有太大变化)。

• bos比order多出识别订单结构的成本。 • bos比order多出认知交易域业务知识的成本。需要深入了解交易状态,这样才知道什么状态才能去支付(一般是去问order服务的开发),打破了边界。

结论:bos服务不应该承担创建支付单的职责,由order承担最合适。

参考文献

本文参考:微信推送(阿里技术-软件开发必修课:你该知道的GRASP职责分配模式)