架构技能(六):软件设计(下)

news/2025/1/31 6:46:09 标签: 架构技能, 角色, 角色关系, 依赖, 关联, 聚合, 组合

我们知道,软件设计包括软件的整体架构设计和模块的详细设计。

在上一篇文章(见 《架构技能(五):软件设计(上)》)谈了软件的整体架构设计,今天聊一下模块的详细设计。

模块的详细设计,重点体现在需要设计几个具有明确职责的角色,以及角色之间应该设计成什么样的关系,这里的角色一般是一个很大的类;然后对每一个角色继续分析需要设计几个类来实现角色职责,以及类与类之间应该设计成什么样的关系。

模块的详细设计,应该像架构设计一样,由高到低,逐层进行。

在之前的文章(见《架构技能(一):软件架构》)中,分析过软件架构,见下图。

软件架构是指软件系统的顶层结构,包含具有明确职责的角色,这些角色通过相互协作使软件系统提供业务能力。

一个模块应该设计成几个类,取决于业务场景;但是类与类之间的关系是固定的模式,从原子化不可再分的角度看,共包括六类关系:依赖关联聚合组合、继承、实现;“继承” 和 “实现” 更多是为了多态特性的实现需要,在基础框架中应用普遍,在业务系统中以前四种类关系应用为主。

依赖

依赖方与被依赖方是 “使用” 关系,从代码上看,被依赖方对象往往出现在依赖方对象的方法内部。比如:依赖方是 Person,被依赖方是 Bus,人 “使用” 公共汽车,见下图。

关联

关联方与被关联方是 “拥有” 关系,从代码上看,被关联方对象往往出现在关联方对象的成员变量上。比如:关联方是 Person,被关联方是 Phone,人 “拥有” 手机,见下图。

聚合

聚合方与被聚合方是整体与个体之间的关联关系,整体 “拥有” 个体,个体 “聚合” 成整体。从代码上看,被聚合方对象出现在关联方对象的成员变量上。比如:聚合方是 Company,被聚合方是 Staff,公司 “拥有” 员工,员工 “聚合” 成公司,见下图。

组合

组合方与被组合方仍是整体与个体之间的关联关系,整体 “拥有” 个体,个体 “组合” 成整体,与聚合关系不同的是,被组合方不能独立存在。从代码上看,被组合方对象出现在组合对象的成员变量上,且在组合对象内部进行初始化。比如:组合方是 Computer,被组合方是 Cpu,计算机 “拥有” CPU,CPU “组合” 成了计算机,且 CPU 离开计算机不能独立存在,见下图。

总结一下在软件模块详细设计中,类与类之间最常用的四种关系:

  • 依赖的核心是 “使用”,两个对象在同一层次上;

  • 关联的核心是 “拥有”,两个对象在同一层次上;

  • 聚合的核心是 整体与个体之间的 “拥有” 与 “聚合”,个体离开整体后对象的生命周期可以继续;

  • 组合的核心是 整体与个体之间的 “拥有” 与 “组合”,个体离开整体后对象的生命周期结束。

理解这四类对象关系,并烂熟于心,那就可以对软件业务系统模块进行详细设计了。

这里,我们以 基于时间轮的 HTTP 长轮询获取消息为例,描述软件模块详细设计的过程。

先简述一下 HTTP 长轮询,见下图。

  • http 客户端向 http 服务端发起 http 请求;

  • http 服务端 hold 住该请求,不会立刻返回 http 响应;

  • http 服务端只有满足两个条件中的任何一个才会返回 http 响应: 要么超过一定时间(超时),要么产生了属于客户端的消息

  • http 客户端收到响应后,再次发起 http 请求,重复上述过程。

在这个场景里, http 服务端应该如何设计呢?

分析上述时序图,可以很容易得出:http 服务端需要包含三个角色,即 http服务、消息模块和时间轮模块,三个角色之间的关系,见下图。

  • http 客户端向 http 服务端发起 http 请求;

  • http 服务端 hold 住该请求,不会立刻返回 http 响应;

  • http 服务端只有满足两个条件中的任何一个才会返回 http 响应: 要么超过一定时间(超时),要么产生了属于客户端的消息

  • http 客户端收到响应后,再次发起 http 请求,重复上述过程。

在这个场景里, http 服务端应该如何设计呢?

分析上述时序图,可以很容易得出:http 服务端需要包含三个角色,即 http服务、消息模块和时间轮模块,三个角色之间的关系,见下图。

  • 时间轮对于 http 服务来说是一个工具,http 服务使用时间轮这个工具进行管道的注册,所以 HttpService 类 “依赖” 于 Wheel 类;

  • 消息模块产生新的消息时,根据客户端的 uid 从时间轮中获取客户端的管道,所以 MsgManager 类 “依赖“ 于 Wheel 类;

  • 消息模块与 http 服务之间没有直接关系,消息模块只是通过管道对象将消息传输给 http 服务而已。

明确了第一层的类关系,就可以对第二层的类关系进行展开分析和设计了。http 服务与消息模块留给大家进行设计,我们看时间轮模块如何设计。

对类内部的设计,需要从类对外提供的能力来入手分析。

通过对 http 服务、消息模块、时间轮模块三个角色之间的关系分析,我们知道时间轮对外提供了三项能力:

  • 对客户端管道对象的注册;

  • 对客户端超时信息的发送;

  • 根据客户端 uid 获取用户的管道。

注册客户端管道时,需要包括用户 uid、用户管道、注册时的时间轮刻度等信息;对客户端超时信息发送时,需要根据时间轮刻度找到用户管道;而获取用户管道时,需要根据客户端 uid 获取。所以,为了方便查询,用户 uid 与时间轮时间应该是一个双向映射的关系;同时,便于维护,将用户管道与时间轮时间封装成用户信息对象。

这里的管道对象,在不同的语言中可以采用不同的实现方式,比如 Go 语言中可以采用 chan ,Java 语言中可以采用队列。时间轮模块内部原理,在之前的文章(见《单体架构 IM 系统之长轮询方案设计》)中有详细描述,见下图。

时间轮模块类 Wheel 内部应包含三个类: 时间盘(TimeDisk)、用户信息映射(UserInfoMap)、时间轮刻度映射(TimeUserMap),这几个类之间的关系,见下图:

        

  • 时间轮对象 “拥有” 时间盘、用户信息映射和时间轮刻度映射三个对象,这三个对象组合成了时间轮对象,所以 Wheel 与 TimeDisk、UserInfoMap、TimeUserMap 是组合关系,离开了 Wheel,这三个对象毫无意义;

  • 时间盘 TimeDisk 对象,提供时间指针每秒走一格的能力;下一格的时间刻度所映射的所有客户端列表就是超时的客户端;

  • 用户信息映射 UserInfoMap 对象提供用户 uid 对用户信息对象的映射能力;

  • 时间轮刻度映射 TimeUserMap 对象提供时间轮时间刻度对用户 uid 列表的映射能力;

  • TimeDisk、UserInfoMap、TimeUserMap 三个对象之间没有任何关系。

我们继续对 UserInfoMap 设计第三层类关系,TimeDisk 和 TimeUserMap 留给大家分析和设计,见下图。

  • UserInfoMap “拥有” UserInfo,UserInfo “聚合” 成了 UserInfoMap,UserInfoMap 和 UserInfo 是聚合关系;

  • UserInfo 中封装了管道 UserChan 对象,UserInfo “拥有” UserChan 对象,两者是关联关系。

对业务模块的类关系,通常设计到第三层,整个类关系脉络应该就非常清晰了;http 服务端,将上述三层类关系进行整合,见下图。

在这个简单的整体类关系图中,依赖关联聚合组合四种类关系全部进行了应用。明确了整个模块中关键类之间的关系后,剩下的就是对方法逻辑的编写了,这对于初级程序员来说就不是个事了!

最后,总结文中关键:

  1. 模块的详细设计,重点体现在需要设计几个具有明确职责的角色(类),以及角色(类)之间应该设计成什么样的关系;

  2. 模块的详细设计,应该像架构设计一样,由高到低,逐层进行;

  3. 类之间包括六类关系:依赖关联聚合组合、继承和实现,在业务系统中以前四类为主:

    (1)依赖的核心是 “使用”;

    (2)关联的核心是 “拥有”;

    (3)聚合的核心是 整体与个体之间的 “拥有” 与 “聚合”,个体离开整体后对象的生命周期可以继续;

    (4)组合的核心是 整体与个体之间的 “拥有” 与 “组合”,个体离开整体后对象的生命周期结束;

  4. 对类内部的设计,需要从类对外提供的能力来入手分析;

  5. 基于时间轮方案实现的 http 长轮询中,关键类之间的关系如下:

    (1)HttpService 依赖 Wheel;

    (2)MsgManager 依赖 Wheel;

    (3)Wheel 组合 TimeDisk;

    (4)Wheel 组合 UserInfoMap;

    (5)Wheel 组合 TimeUserMap;

    (6)UserInfoMap 聚合 UserInfo;

    (7)UserInfo 关联 UserChan。


http://www.niftyadmin.cn/n/5838419.html

相关文章

【Linux】21.基础IO(3)

文章目录 3. 动态库和静态库3.1 静态库与动态库3.2 静态库的制作和使用原理3.3 动态库的制作和使用原理3.3.1 动态库是怎么被加载的 3.4 关于地址 3. 动态库和静态库 3.1 静态库与动态库 静态库(.a):程序在编译链接的时候把库的代码链接到可…

unity使用AVpro插件播放视频,打包安卓系统总是失败

已经排除了中文文件名等问题,只要在工程中添加了AVpro插件(目前是2.6.6版本),在windows上一切正常使用,可以打包输出,但是只要打包安卓就是错误 一次偶然的机会在一台苹果笔记本上用相同的方法做了一个含有…

后端token校验流程

获取用户信息 前端中只有 await userStore.getInfo() 表示从后端获取数据 在页面中找到info对应的url地址,在IDEA中查找 这里是getInfo函数的声明,我们要找到这个函数的使用,所以点getInfo() Override public JSONObject getInfo() {JSO…

讯飞智作 AI 配音技术浅析(二):深度学习与神经网络

讯飞智作 AI 配音技术依赖于深度学习与神经网络,特别是 Tacotron、WaveNet 和 Transformer-TTS 模型。这些模型通过复杂的神经网络架构和数学公式,实现了从文本到自然语音的高效转换。 一、Tacotron 模型 Tacotron 是一种端到端的语音合成模型&#xff…

rust如何操作oracle

首先鄙视甲骨文,这么多钱的公司,不做一个rust库,还要社区帮忙。有个开源的rust库,叫oracle,但是并不是甲骨文做的。 我们来看一个从oracle数据库取所有表和视图的示例: // 定义连接字符串let conn_str1 format!(&quo…

论文阅读(八):结构方程模型用于研究数量遗传学中的因果表型网络

1.论文链接:Structural Equation Models for Studying Causal Phenotype Networks in Quantitative Genetics 摘要: 表型性状可能在它们之间发挥因果作用。例如,农业物种的高产可能会增加某些疾病的易感性,相反,疾病的…

130周四复盘(162)研究神作

1.设计相关 今天没有进行大思想的学习, 而思考的比较细节, 分析了某神作的核心机制的内外逻辑,总结优点,以及一些过时的缺点, b4这款神作就像一座高峰,难以企及,但魂牵梦萦。如果未来有朝一…

从0到1:C++ 开启游戏开发奇幻之旅(二)

目录 游戏开发核心组件设计 游戏循环 游戏对象管理 碰撞检测 人工智能(AI) 与物理引擎 人工智能 物理引擎 性能优化技巧 内存管理优化 多线程处理 实战案例:开发一个简单的 2D 射击游戏 项目结构设计 代码实现 总结与展望 游戏…