文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

软件开发必修课:你该知道的GRASP职责分配模式

2024-12-03 18:16

关注

一 为什么使用RDD?

在RDD中,我们认为“软件对象具有职责”,这个定义很符合人在社会群体中分工协作的方式,软件也是人编写的,所以根据职责思考设计的软件系统符合人的行为习惯,同时更易于理解和管理。在微服务架构中不同系统由不同的组织和人负责,把系统当作对象(人),系统提供的接口就是对象(人)的职责。

职责驱动设计的核心是考虑怎样给对象分配职责,其适用于大到系统、小到对象等任何规模的软件。职责分配的本质是分工,劳动分工是劳动生产率提高的主要原因。

二 如何给对象(元素)分配职责?

分配职责应当从清晰的描述职责开始,对于软件领域对象来说,领域模型描述了领域对象的属性和关联,对应类的属性和引用,用例模型包含一系列的行为活动,对应类的方法。领域模型创建方式可参考《UML和模式应用》、UDD、DDD。

使用GRASP(General Responsibility Assignment Software Patterns)模式分配职责,GRASP是通用职责分配模式,是对一些基本的职责分配原则进行了命名和描述,共9种模式(一些GRASP原则是对其他原则和设计模式的归纳,设计模式有上百种,只是记住GoF 23种设计模式就已经很困难了,更别提还要记住每种模式的细节,因此需要对设计模式进行有效的归类。GRASP中的原则描述了模式的本质,除了有助加速设计模式学习之外,对发现现有设计存在的问题也更有效,这就是归纳的价值)。

当谈论低耦合、高内聚时,我们具体是在谈什么?问题不在于耦合度高、内聚性低,而是在于其产生的负面影响,负面影响往往是在发生变化时体现出来的,这些负面影响会影响到我们开发的效率、稳定性、可维护性、可扩展性、可复用性等等,整个GRASP的核心是如何防止变异(变化)。

在学习过程中发现GRASP缺少结构化的展示归纳结果,通过我自己的理解把开发中常用的GoF设计模式、面向对象设计原则、架构设计原则和GRASP进行关联:

 


注:这个图可能总结的还不够准确,正在逐步学习修改。

 

 

三 GRASP职责分配模式

1 防止变异

该模式基本等同于信息隐藏和开闭原则。如何做到在不修改原来功能的前提下对变化的部分进行扩展?识别不稳定因素是特别困难的,也决定了我们能否做出符合开闭原则的设计。

问题:如何设计对象、子系统和系统,使其内部的变化或不稳定性不会对其他元素产生不良影响。

解决方案:识别预计变化或不稳定之处,分配职责用以在这些变化之外创建稳定接口。

相关原则和模式:

2 低耦合、高内聚

耦合是对某元素与其他元素之间的连接、感知和依赖程度的度量,内聚是对元素职责的相关性和集中度的度量(这里的元素指类、系统、子系统等等),耦合和内聚是从不同角度看待问题,他们互相依赖的互相影响的(以下两点也可以反过来说):

 

低耦合

耦合是对某元素与其他元素之间的连接、感知和依赖程度的度量。这里的元素指类、系统、子系统等等。

问题:怎样降低依赖性,减少变化带来的影响,提高重用性?

解决方案:分配职责,使耦合尽可能低。利用这一原则评估可选方案。

相关模式或原则:

注意:耦合不能脱离专家、高内聚等其他原则独立考虑。

紧密耦合的系统在开发阶段有以下的缺点:

解读:耦合表示元素之间存在依赖,当谈论“耦合高”时,我们具体是在谈论什么呢?是依赖产生的负面影响,所以低耦合的核心是解决不良依赖。高低是度量并不是评判耦合结果好坏的标准,使用“不良耦合”、“松耦合”描述的更为准确。不良耦合产生的负面影响主要有两点:

那么如何做呢?先对依赖关系的好坏进行评估:依赖方式、依赖方向、依赖链路。

 

方向:

链路:

方式:

解决不良依赖:

高内聚

内聚是对元素职责的相关性和集中度的度量。

问题:怎么样保持对象是有重点的、可理解的、可维护的,并且能够支持低耦合?

解决方案:按照相关性分配职责,可保持较高的内聚。

优点:

相关原则和模式:单一职责原则、关注点分离、模块化。

低内聚的缺点:内聚性较低的类要做许多不相关的工作,或需要完成大量的工作,这样的类会导致以下问题:

 

例子:A的变更影响从3个模块变为1个。

 

小结

通过结构化管理来保持低耦合、高内聚。

 

3 创建者

创建者指导我们分配那些与创建对象有关的职责。如此选择是为了保持低耦合。

问题:谁应该负责创建某类的新实例?

解决方案:满足以下条件之一时,将创建类A的职责分配给类B(当满足1条以上时,通常首选包含或聚合)。

优点:支持低耦合,因为创建者和被创建者已经存在关联,所以这种方式不会增加耦合性。

相关模式或原则:

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

例子:

 

4 信息专家(or 专家)

“信息”不单指数据。

问题:给对象分配职责的基本原则是什么?

解决方案:把职责分配给信息专家,它具有实现这个职责所必需的信息

优点:

相关模式或原则:

注意:和“关注点分离”一起使用使得对象进一步内聚,从而达到高内聚,也能降低耦合。

举例:获取所有买的商品总金额,Order和Goods是一对多的关系。

 

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

 

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

5 纯虚构

为了保持良好的耦合和内聚,捏造业务上不存在的对象来承担职责。

问题:当你并不想违背高内聚和低耦合或者其他目标,但是基于专家模式所提供的方案又不合适时,哪些对象应该承担这一职责?

解决方案:对人为制造的类分配一组高内聚的职责,该类并不代表问题领域的概念--虚构的事物,用以支持高内聚、低耦合和复用。

优点:

相关原则和模式:

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

 

通过虚构对象GoodsItems承担和商品聚合计算相关的职责。

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

6 控制器

解决方案:把职责分配给能代表以下选择之一的类:

相关模式:

控制器的核心是提供一个统一入口,避免客户对元素内部进行耦合,很好的维护了边界:

7 多态

问题:如何处理给予类型的选择?如何创建可插拔的软件构件?

解决方案:当相关选择或行为随类型有所不同时,使用多态操作为变化的行为类型分配职责。

优点:可扩展性强,同时不影响客户。

相关原则和模式:

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

 

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

8 间接性

计算机学科中的大多数问题都可以通过增加一层解决,如果不行再加一层。反过来大多数性能问题都可以通过去掉一层来解决。

问题:为了避免两个或多个事物之间直接耦合,应该如何分配职责?

解决方案:将职责分配给中介对象,使其作为其他构建或服务之间的媒介,以避免他们之间的直接耦合。

优点:实现了构件之间的低耦合。

相关原则和模式:

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

四 架构模式

除了职责分配原则,还需要一些架构模式帮助我们更好的落地。

1 分层架构

在分布式系统中系统是独立存在的,可以单独变更而不对其他系统产生影响,但是随着业务整体复杂度的提升也带来了一些负面影响:由于整体被分解成大量独立的系统,随着复杂度提升系统之间的依赖关系会变的错综复杂,某个系统的变更会影响其他系统,同时也会产生意想不到的问题,效率也随之下降。这时就需要重新对分布式系统的逻辑架构做设计,以解决系统间的不良耦合和内聚,从而提效。

分层架构是非常实用和常见的方式,TCP/IP、HTTP、操作系统等等都运用了分层,分层的本质很简单:通过分离关注点,达到高内聚;通过向下依赖、拒绝循环依赖、使用接口,达到低耦合。

 

分层架构也是存在缺点的:按照分层架构定义消息消费应该在基础设施层,但是消息消费是为了执行某个业务逻辑,这样就需要依赖应用层 或 领域层,如果真的这样写就会出现循环依赖问题。通过依赖倒置可以解决依赖问题。

2 六(多)边形架构(洋葱圈架构)

六边形架构(Hexagonal Architecture),又称为端口和适配器架构风格,其中的“六”具体数字没有特殊的含义,仅仅表示一个“量级”的意思,六边形的定义只是方便更加形象的理解。

 

六边形架构提倡用一种新的视角来看待整个系统,该架构中存在两个区域:“外部区域”和“内部区域”。在外部区域中不同的客户均可以提交输入(网络请求、定时脚本、消息消费等),而内部区域则是处理具体逻辑的地方。

图源:https://www.jianshu.com/p/d3e8b9ac097b

 

五 案例

案例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的地方都做替换:

结论:由于变更涉及到70多个类,同时事务管理器获取方式也需要修改,其带来的影响还是挺大的,不满足“低耦合”原则,可以使用“多态”原则重新设计。

案例2:订单对应的支付单应该由谁来创建?

拿饿了么交易系统举例,当前创建支付单的职责是由bos服务承担(面向app的一个后端服务)的,接下我们进行分析。

 

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

支付单创建依赖:

 

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

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

【本文为51CTO专栏作者“阿里巴巴官方技术”原创稿件,转载请联系原作者】

​戳这里,看该作者更多好文​

 

来源:51CTO专栏内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯