《系统架构设计》-04-架构模式(Architecture Pattern)
架构模式和架构风格的区别
- 架构模式
基于子系统或模块及其之间的关系层次,描述了架构粗粒度的解决方案,着重描述系统的内部组织。- 架构风格
描述的是系统组织方式的惯用方式,是系统组织性的设计,可以认为风格是模式的外在表现。
1. 数据访问
数据访问要解决的问题:
- 如何把持久化媒介中的数据模型与应用程序中的数据模型相对应
- 如何有效地设计基于业务领域的数据访问方式
1.1 数据映射器
1.1.1 概念
- 数据映射器(Data Mapper):在保持对象与数据库彼此独立的情况下在二者之间移动数据的一个映射器层。
- 实现方法:分离接口以处理查找方法,然后把数据映射到领域对象。
1.1.2 示例
图中应用程序App需要访问Student对象对应的持久化数据
提供Student Date Mapper接口供App使用。该接口的实现类Student Data Mapper Impl完成内存Student对象与持久化媒介中数据的映射并抛出该过程中可能出现的异常。
1.2 数据仓库
1.2.1 概念
- 数据映射层只是提供了内存对象和持久化媒介之间的数据转换,其本身并没有提供数据访问的入口
- 数据访问的入口由数据仓库(Repository)提供并实现。
- 应用程序可以利用接口来访问领域对象,而不需要关注具体的持久化实现。
1.2.2 示例
示例说明:针对一个账户管理模块,使用用户名或年龄范围查询
通过数据仓库模式,我们就可以构建两个不同的Specification分别代表这两种查询对象。如图:
- 把两个查询对象命名为AccountSpecificationByUserName和AccountSpecificationByAgeRange
- SqlRepository和Sql Specification说明该数据仓库的持久化媒介是关系型数据库。
2. 服务定位(Service Locator)
2.1 概述
使用服务注册(Registration)和发现(Discovery)机制解耦服务提供者和用户。应用程序无需直接访问具体的服务提供者类。
2.2 组件
- 服务(Service)
实际处理请求,可以包含不同的实现。对这种服务的引用可以在服务器的中央注册中心中查找。 - 上下文(Context)
上下文中带有对要查找的服务的引用 - 服务定位器(Service Locator)
服务定位器是通过中央注册中心查找并获取服务,同时可以根据需要对服务进行缓存。 - 缓存(Cache)
缓存存储服务的引用以便复用。 - 客户端(App)
App是通过Service Locator调用服务的对象。
3. 异步化
3.1 生产者-消费者(Producer-Consumer)模式
实现该模式的基本思路是使用多线程操作,即
- 如果资源队列满,则调用wait方法释放锁
- 等待到资源队列有空缺,然后在资源队列加入新的元素并醒其他等待的线程。
对于应用程序而言因为其复杂性远大于所带来的效果,因此我们并不鼓励使用这种底层操作构建多线程系统,。
3.2 半同步半异步
3.2.1 概念
- 应用层使用同步IO模型,简化编程
- 底层使用异步IO模型,确保高效执行
3.2.2 示例
前提:
- AsyncTask是一个代表能够异步执行的接口
- ArithmeticSumTask实现该接口并提供对数字n进行从1到n的求和操作。
实验要求:
在App端,同步调用3次ArithmeticSumTask所提供的方法。我们希望这3次调用能够并行执行。
方案如图:
- AsynchronousService面向App并封装了ExecutorService的执行入口。
- 当App端同步执行ArithmeticSumTask时,实际上就是调用ExecutorService中的线程池实现了异步操作,确保同步调用的操作最终产生异步执行的效果。
4 资源管理
-
资源(Resource):如数据库会话、网络连接、分布式服务和组件等
-
资源管理的基本需求:性能、可伸缩性、灵活性等。
-
资源管理:它包含一组模式,代表着资源从创建到销毁的整个过程
被认为是实现资源管理基本需求的有效方式,广泛应用于面向系统构建过程中资源管理的各个场景。
4.1 资源生命周期管理模式
资源生命周期(Lifecycle)管理模式通常表现为一个容器(Container)。该容器提供外部系统访问资源的入口。
如上图:
- 容器中保存着各种资源对象
- 客户端可以通过查询获取特定资源并使用该资源
- 资源对象本身的创建、删除等生命周期由容器自动完成,对客户端完全透明
4.2 资源有效性管理模式
- 目的:最大化复用现有的资源以满足特定场景下的资源利用需求。
系统中一般都会存在数据库会话、网络连接等非托管资源(Unmanaged Resouce)。这些资源的生命周期不受容器管理,而且往往需要较大的创建和销毁成本。如果对这些资源不加控制,任由应用程序无限制地使用,很容易导致出现性能和可用性问题。
- 基本思路:“池化”操作
初始化一个具有一定容量的资源池。处于资源池中的对象可以重复利用,从而避免应用程序每次使用资源都需要创建和销毁新对象。
4.2.1 对象池(Object Pool)
对象池适用于类的实例化过程开销较大、类的实例化的频率较高的场景。
- 优点:对象池节省了创建类的实例的开销
- 缺点:存储空间随着对象的增多而增大,
需要一个来控制池大小的特定策略以达到时间和空间的平衡。
各种数据库连接池是对象池的代表实现机制,负责维护对象池,包括初始化对象池、扩充对象池的大小、重置归还对象的状态等。用户从池中获取对象,对象池对于用户来讲是透明的,但是用户必须遵守这些对象的使用规则,使用完对象后必须归还或者关闭对象。
4.2.2 线程池(Thread Pool)
- 核心组件
-
线程池管理器(Pool Manager)
用于创建并管理线程池,包括创建线程池、销毁线程池、添加新任务 -
工作线程(Pool Worker)
线程池中线程,在没有任务时处于等待状态,可以循环的执行任务; -
任务接口(Task)
每个任务必须实现的接口,以供工作线程调度任务的执行,主要规定任务的入口、任务执行完后的收尾工作、任务的执行状态等 -
任务队列(Task Queue)
用于存放没有处理的任务,提供一种缓冲机制。
- 使用场景
创建时间 + 销毁时间 远大于 执行任务时间
4.2.3 资源创建策略模式
-
核心思想:
将一个复杂对象的构建算法与它的组件及组装方式分离,使得构建算法和组装方式可以独立应对变化。复用同样的构建算法可以创建不同的表示,不同的构建过程同样可以复用相同的组件组装方式。 -
Builder模式
- 资源创建策略模式隐藏对象的内部表示,仅提供接口来创建对象,而隐藏该对象是如何通过对象部件进行的装配过程。
- 将构造代码和表示代码分开,每一个具体对象包含了创建和装配该特定对象的所有代码,提供不同的具体对象就可以构建出不同的Builder结果。
4.2.4 资源获取策略模式
-
资源获取策略
- 延迟获取(Lazy Acquisition)
- 尽快获取(Eager Acquisition)
- 部分获取(Partial Acquisition)
-
示例(延迟获取策略)
如下图所示:某个数据对象由DataA和DataB两部分数据组成,同时数据B部分的获取成本较高。当客户端在某个场景使用该对象时,可能只需要获取对象的数据A部分就可以满足当前场景的数据需求。这样数据B就可以使用延迟获取策略暂时不进行加载,而仅当同时需要数据A和数据B两部分数据的场景之时再进行获取。
4.2.5 资源释放策略模式
-
模式举例
- 自动垃圾回收(Auto Garbage Collection)机制之外
- 引用计数(Reference Count)机制
-
引用计数
就是每个对象都有一个计数用来表明有多少其他的对象目前正保留着对它的引用(Reference)。对象A想要引用对象B,A就把B的Reference Count加1,而当A结束了对B的引用,A就把B的Reference Count减1。当没有任何对象再引用B时,B的Reference Count就减为0,B就被清除,内存就被释放。清除B的时候,被B所引用的对象的Reference Count也可能减小,也可能使它们被清除。
5. 依赖管理
5.1 基本依赖关系
可分为:直接依赖、间接依赖、循坏依赖(应消除)
5.2 消除循环依赖
-
系统设计中不应该存在循环依赖。
-
消除思路:就是通过在两个相互循环依赖的组件之间添加中间层,变循环依赖为间接依赖。
5.2.1 上移
实现
把两个相互依赖组件中的交互部分抽象出来形成一个新的组件(新组件同时包含着原有两个组件的引用)
示例(消除循环依赖)
- 如图,假设一个有循环依赖的服务,我们需要消除它的循环。
说明:
User和Order之间就存在循环依赖关系
- User对象可以创建Order对象并保持Order对象列表
- Order对象同样需要使用User对象,并根据User对象中的打折(Discount)信息计算Order金额
- 上移策略对
引入了一个新的组件Mediator,提供的pay Order方法对循环依赖进行了剥离。
该方法同时使用Order和User作为参数并实现了Order中根据User的打折信息进行金额计算的业务逻辑
Mediator组件消除了Order中原有对User的依赖关系并在依赖关系上处于User和Order的上层。
5.3.2 下移
实现
- 抽象出一个Calculator组件专门包含打折信息的金额计算方法。
- 该Calculator由User创建,并注入Order的pay方法中去,
说明
- 原有的Order对User的依赖关系就转变为Order对Calculator的依赖关系
- 而User因为是Calculator的创建者同样依赖于Calculator。
这种生成一个位于User和Order之下但能同样消除Order中原有对User的依赖关系的组件的策略,就称之为下移。
5.3.3 回调(Callback)
实现
- 本质上就是一种双向调用模式,也就是说,被调用方在被调用的同时也会调用对方。
- 在面向对象的语言中,回调通常是通过接口或抽象类的方式来实现。
示例
- 抽象出一个Calculator接口用于封装金额计算逻辑,该接口与Order处于同一层次,而User则实现了该接口。
- 这样Order对User的依赖就转变成Order对Calculator接口的依赖,也就是把对User的直接依赖转变成了间接依赖。
UML类图的相关说明:
- 带三角形的虚线代表实现
- 带虚线的箭头表示依赖。