【iSAQB软件架构】架构模式
模式在软件的设计和开发中是一个重要的工具。在软件开发的许多领域都存在模式——例如,设计模式、架构模式、分析模式、软件组织模式和教学模式。
架构模式的分类是按照弗兰克·布施曼(Frank Buschmann)的四类系统进行的。其基本概念是以模式所解决的问题作为分类的基础。
四类系统分别为:
- 适应性系统
- 交互系统
- 从混沌到结构
- 分布式系统
适应性系统
此类别中的模式支持应用程序的扩展以及它们对不断发展的技术和不断变化的功能需求的适应。
依赖注入
在面向对象设计中,由于需要创建一个抽象接口的具体实例,经常会出现问题。
• 谁管理所使用实例的生命周期?
• 谁决定在运行时最终实例化哪个具体的类?
为此,此模式提供了一个独立的构建块,即:装配器。
装配器在运行时确定如何解决上述问题。装配器将依赖对象的特定实例的引用传递过去。它可以被视为一种“通用工厂”。
它首先检查 ServiceUser 对于必要的依赖项(Service),并通过元信息生成或确定提供所需服务的 ServiceImplementation。然后,它将此服务实现“注入”到 ServiceUser 中,从而将类与其依赖项解耦。
已有的用于依赖注入的 Java 实现有:
• JEE 6:上下文和依赖注入(JSR - 299)
• Spring框架
核心组件解析
-
ServiceUser
(服务使用者 / 客户端类):- 这是需要依赖其他对象(服务)才能完成其功能的类。
- 它依赖于一个抽象接口
Service
(通过<<interface>> Service
表示依赖关系,通常是关联或聚合关系,图中用-->>
表示)。 ServiceUser
不负责创建或查找它所依赖的Service
具体实现。这就是解耦的关键。
-
<<interface>> Service
(服务接口):- 这是一个抽象接口,定义了
ServiceUser
所需的服务契约(方法签名)。 - 它代表了
ServiceUser
所依赖的服务的抽象类型。 - 依赖抽象(接口)而非具体实现是实现松耦合的重要原则。
- 这是一个抽象接口,定义了
-
ServiceImplementation
(服务实现类):- 这是
Service
接口的一个具体实现类。 - 它提供了
Service
接口定义的功能的实际逻辑。 - 图中用
◁|--
(空心三角箭头+虚线) 或类似的实现(Realization) 关系线指向<<interface>> Service
,表示它“实现了”该接口 (<realize>
)。
- 这是
-
Assembler
(装配器 / 注入器 / DI容器):- 这是依赖注入模式中的核心协调者,扮演注入器(Injector) 或容器(Container) 的角色。
- 它的职责是:
- 解析依赖: 根据元信息(配置、注解、约定等)确定
ServiceUser
所需的Service
接口应该由哪个具体实现类(这里是ServiceImplementation
) 来提供。 - 创建实例: 实例化
ServiceImplementation
(以及它可能依赖的其他对象)。 - 注入依赖: 将创建好的
ServiceImplementation
实例(作为Service
接口的实现)“注入” 到ServiceUser
对象中。注入通常通过构造函数、Setter方法或字段赋值完成。
- 解析依赖: 根据元信息(配置、注解、约定等)确定
依赖注入工作流程:
- 识别依赖:
Assembler
识别出ServiceUser
依赖于Service
接口。 - 查找实现:
Assembler
根据配置或规则,找到Service
接口对应的具体实现类是ServiceImplementation
。 - 创建实例:
Assembler
创建ServiceImplementation
的实例。 - 注入依赖:
Assembler
将ServiceImplementation
实例传递给ServiceUser
。这可以通过:- 构造函数注入: 在创建
ServiceUser
时,将ServiceImplementation
实例作为参数传给ServiceUser
的构造函数。 - Setter注入: 创建
ServiceUser
后,调用ServiceUser
的 setter 方法(例如setService(Service s)
)将ServiceImplementation
实例传入。 - 字段注入(较少推荐): 直接通过反射等方式设置
ServiceUser
内部持有Service
接口的字段。
- 构造函数注入: 在创建
- 使用服务:
ServiceUser
现在持有了一个实现了Service
接口的对象(ServiceImplementation
),可以调用其方法来使用所需的服务功能,而无需关心该对象是如何创建或从哪里来的。
关键优势 (解耦):
ServiceUser
与ServiceImplementation
解耦:ServiceUser
只依赖于稳定的接口Service
,完全不知道ServiceImplementation
的存在。ServiceImplementation
可以被替换成任何其他实现了Service
接口的类(例如AnotherServiceImplementation
或用于测试的MockServiceImplementation
),而不需要修改ServiceUser
的代码。- 控制反转 (IoC): 对象的创建和依赖关系的组装控制权从
ServiceUser
内部反转到了外部的Assembler
。ServiceUser
不再负责管理自己的依赖项的生命周期和获取。 - 提高可测试性: 在测试
ServiceUser
时,可以轻松注入一个模拟的Service
实现 (MockService
),专注于测试ServiceUser
本身的逻辑,而不受真实ServiceImplementation
行为的影响。 - 提高可维护性和灵活性: 系统组件更容易替换、复用和配置。依赖关系清晰,集中管理在
Assembler
(或其代表的DI容器)中。
总结:
您描述的过程完美体现了依赖注入的精髓:一个类(ServiceUser
)不应该负责创建它依赖的对象(ServiceImplementation
),而是应该由外部实体(Assembler
)提供(注入)它所依赖的抽象(Service
接口)的具体实现。 这种模式通过将对象的创建、查找和绑定与其使用分离,极大地降低了类之间的耦合度,提高了代码的模块化、可测试性和可维护性。Assembler
的角色在实际应用中通常由专门的 DI框架或容器(如 Spring, Guice, .NET Core DI 等) 来承担。
下面是一个完整的Java代码示例,展示基于您描述的依赖注入模式实现:
// 1. 服务接口 (抽象)
interface MessageService {void sendMessage(String message, String recipient);
}// 2. 服务实现类 (具体实现)
class EmailService implements MessageService {@Overridepublic void sendMessage(String message, String recipient) {System.out.println("发送邮件给 " + recipient + ": " + message);// 实际邮件发送逻辑...}
}class SMSService implements MessageService {@Overridepublic void sendMessage(String message, String recipient) {System.out.println("发送短信给 " + recipient + ": " + message);// 实际短信发送逻辑...}
}// 3. 服务使用者 (依赖抽象接口)
class NotificationService {private final MessageService messageService;// 通过构造函数注入依赖public NotificationService(MessageService service) {this.messageService = service;}public void sendNotification(String message, String recipient) {// 使用注入的服务实现messageService.sendMessage(message, recipient);}
}// 4. 装配器 (依赖注入容器)
class ApplicationAssembler {public static void main(String[] args) {// 根据配置或环境决定使用哪种实现MessageService service = createMessageService();// 将依赖注入到使用者NotificationService notifier = new NotificationService(service);// 使用组装好的服务notifier.sendNotification("您的订单已发货", "13800138000");}private static MessageService createMessageService() {// 这里可以根据配置文件、环境变量等动态选择实现String serviceType = System.getProperty("message.service", "SMS");if ("EMAIL".equalsIgnoreCase(serviceType)) {return new EmailService();} else {return new SMSService();}}
}// 5. 测试类 (展示解耦优势)
class NotificationServiceTest {public static void main(String[] args) {// 创建模拟服务实现 (用于单元测试)MessageService mockService = new MockMessageService();// 注入模拟服务NotificationService testNotifier = new NotificationService(mockService);// 执行测试testNotifier.sendNotification("测试消息", "test@example.com");}// 模拟服务实现 (不需要实际发送消息)static class MockMessageService implements MessageService {@Overridepublic void sendMessage(String message, String recipient) {System.out.println("[模拟] 消息已发送给 " + recipient);// 这里可以添加断言验证逻辑}}
}
关键点解析:
-
依赖抽象(DIP原则)
// 客户端依赖抽象接口 class NotificationService {private final MessageService messageService; // 依赖抽象 }
-
依赖注入(三种方式)
- 构造函数注入(示例使用的方式)
public NotificationService(MessageService service) {this.messageService = service; }
- Setter方法注入
public void setMessageService(MessageService service) {this.messageService = service; }
- 字段注入(不推荐,破坏封装性)
-
装配器职责
// 创建具体实现 MessageService service = createMessageService();// 注入依赖 NotificationService notifier = new NotificationService(service);
-
解耦优势体现
- 更换实现无需修改客户端:
// 只需修改装配器创建的对象 MessageService service = new EmailService(); // 替换实现
- 测试时使用模拟服务:
// 在测试中注入模拟对象 NotificationService testNotifier = new NotificationService(mockService);
-
运行时动态决策
// 根据配置选择实现 String serviceType = System.getProperty("message.service", "SMS");
实际框架应用
在实际项目中,通常会使用DI框架(如Spring)代替手工装配:
// Spring框架示例
@Configuration
public class AppConfig {@Beanpublic MessageService messageService() {return new SMSService(); // 或EmailService}@Beanpublic NotificationService notificationService(MessageService service) {return new NotificationService(service); // 自动注入}
}// 客户端类
@Service
public class NotificationService {@Autowired // 自动注入依赖private MessageService messageService;
}
这个示例完整展示了:
- 依赖抽象(面向接口编程)
- 控制反转(IoC)
- 依赖注入(DI)
- 解耦带来的可测试性和可扩展性优势
- 装配器的核心作用
通过这种模式,当需要更换消息服务(如从短信切换到邮件)时,只需修改装配器的配置,而无需修改NotificationService
的任何代码。
交互系统
交互系统模式支持交互式软件系统的结构化。
模型-视图-控制器(Model-view-controller MVC模式)
将应用程序移植到不同的平台不应导致整个应用程序的重构。在这里,目标是简单地更改或扩展以及重用各个组件。
用户界面经常变化。相同的信息必须以不同的方式在不同的窗口中提供,导致所需框架的复杂性。不同的用户组需要不同的布局或格式。在模型的一致视图和过度更新导致的性能问题之间很难取得平衡。
为了解决这个问题,用户界面被分为三个责任区域。模型封装了通常稳定的业务逻辑及其数据。视图组件提供模型的视图。控制器处理用户事件,执行相应的业务逻辑,并触发视图组件的更新。
这种方法的一个很好的例子是电子表格程序,它为相同的数据模型提供了详细的表格视图和易于理解的图表视图。
模型-视图-展示器(Model-view-presenter MVP模式)
模型 - 视图 - 展示器(MVP)是一种面向用户界面应用的交互式软件系统的架构模式。它基于模型 - 视图 - 控制器模式,侧重于严格分离用户界面和业务逻辑(分离视图和模型)。此模式背后的原则是将应用程序分解为三个组件:
• 模型包含业务逻辑和视图所需的所有数据。它仅由展示器控制,既不知道视图也不知道展示器。
• 视图代表表示层,设计极其简单。它绝对不包含控制逻辑,仅负责展示和接收用户输入。
• 展示器将视图链接到模型,并控制各层之间的逻辑流。它收集所有应用程序用例,接受来自视图的用户输入,调用模型中的方法,并在视图中设置数据。
由于表示层结构简单,视图可以被其他视图替换。通过这种方式,系统可以进行修改以便在不同平台上使用,并且与 MVC 相比,易于测试。自 2004 年马丁·福勒(Martin Fowler)定义以来,MVP 已被广泛用于客户端的开发。
表示-抽象-控制(Presentation-Abstraction-Control ,PAC模式)
应用程序功能的增加也会增加用户界面的复杂性。对于复杂的用户界面,不同的功能区域可能会相互混杂,从而降低了可维护性。此外,简单的分解(如在 MVC 模式中)会导致,当所有用户事件都由单个控制器处理时,响应时间不令人满意等问题。
在这种模式中,用户界面的结构被分解为分层协作的“代理”。中级使用基本功能,然后为用户界面的各个底层元素提供功能。每个代理都由一个控制器、抽象和视图组成。控制器是代理与层次结构中上下层代理的接口,并控制其责任区域。抽象将完整模型的部分适配为一个本地模型,该本地模型仅包含本地视图所需的元素。这种严格的分层分离能够在用户界面内实现处理操作的并行化,特别是在只有完整模型的部分可用的情况下。
Eclipse IDE 就是一个很好的例子:工作区提供了菜单栏、工具栏、工作区和状态栏。透视图将这些作为嵌入的操作元素提供给每个透视图的主题区域使用。这些包括边距视图和编辑器窗口的区域,然后由特定的内容编辑器和视图用它们自己的菜单项填充。
MVP(Model-view-presenter ) 的优势和使用场景
核心差异图示总结
架构 | 交互关系 | 依赖方向 |
---|---|---|
MVC | View ↔ Controller ↔ Model 允许 View 直接访问 Model | 双向依赖(View 可能依赖 Model) |
MVP | View ↔ Presenter ↔ Model View 与 Model 完全隔离 | 单向依赖(View 仅依赖 Presenter 接口) |
您的图示清晰体现了这一关键差异:MVP 中 View 和 Model 无直接连接,Presenter 居中协调。
MVP 的三大核心优势
-
彻底解耦 View 与业务逻辑
- 问题(MVC):View 可直接调用 Model,导致 UI 与业务逻辑混杂(如:在界面代码中处理数据格式转换)。
- 解决(MVP):
- View 仅通过 接口与 Presenter 通信(例:
IUserView.showUserName(String name)
)。 - Presenter 处理所有业务逻辑(数据获取、格式化、校验),View 只负责渲染结果。
- 结果:更换 UI 框架(Web → 移动端)时,只需重写 View 层,Presenter 和 Model 无需修改。
- View 仅通过 接口与 Presenter 通信(例:
-
可测试性显著提升
- 问题(MVC):Controller 常与 View/框架强耦合(如依赖 Servlet API),难以单元测试。
- 解决(MVP):
- Presenter 仅依赖 View 接口,可通过 Mock View 进行纯逻辑测试。
- 示例测试用例:
// 测试 Presenter 是否正确处理了用户登录 @Test void testLoginSuccess() {MockUserView mockView = new MockUserView(); // 模拟 ViewUserPresenter presenter = new UserPresenter(mockView, new UserModel());presenter.onLoginClick("user", "pass123");assertTrue(mockView.showSuccessWasCalled); // 验证 View 接口被正确调用 }
-
避免“巨型 Controller”问题
- 问题(MVC):复杂业务中 Controller 易膨胀成“上帝对象”,维护困难。
- 解决(MVP):
- Presenter 按功能拆分(例:
UserProfilePresenter
、OrderHistoryPresenter
)。 - 每个 Presenter 专注单一业务流,代码更内聚。
- Presenter 按功能拆分(例:
MVP 的典型使用场景
-
桌面应用(Windows Forms, Java Swing)
- 需求:复杂表单验证、多步骤操作。
- MVP 优势:将状态管理/校验逻辑移入 Presenter,保持 View 简单。
-
需要高可测试性的应用
- 场景:金融/医疗等业务逻辑复杂的系统。
- MVP 优势:Presenter 可无 UI 环境测试,保障核心逻辑正确性。
-
多平台共享业务逻辑
- 场景:同一套业务需同时支持 Android、iOS、Web。
- MVP 实现:
- 核心 Presenter 和 Model 复用。
- 为每个平台实现不同的 View 层。
-
遗留系统重构
- 问题:老旧代码中 UI 与逻辑深度耦合。
- 重构步骤:
① 定义 View 接口(暴露所需操作)。
② 抽离逻辑至 Presenter。
③ 原 UI 类转为 View 接口的实现。
何时选择 MVC 而非 MVP?
- 简单应用:CRUD 操作为主的系统,MVC 更轻量。
- 框架内置 MVC:Ruby on Rails、Spring MVC 等框架已优化了 MVC 的使用体验。
- 数据绑定支持:现代前端框架(如 Vue/React)通过 MVVM 解决了 View-Model 耦合问题。
总结:MVP 的核心价值
维度 | MVC | MVP |
---|---|---|
View 职责 | 可能包含业务逻辑 | 仅负责 UI 渲染 |
可测试性 | 较低(依赖 UI 框架) | 高(Presenter 可独立测试) |
维护成本 | 复杂业务中易失控 | 逻辑集中,更易维护 |
适用平台 | Web 框架、简单应用 | 桌面应用、跨平台核心逻辑 |
建议选择 MVP 当:
✅ 应用业务逻辑复杂
✅ 需要高频单元测试
✅ 计划支持多平台 UI
✅ 长期维护性至关重要
与 MVP 相比,MVC(Model-View-Controller) 的核心优势在于轻量、开发效率高、与主流框架深度集成,尤其适合业务逻辑简单的快速开发场景。以下是具体分析:
MVC 的三大核心优势
-
更低的认知与实现成本
- 直观性:MVC 的分层逻辑(用户操作 → Controller → 更新 Model → 渲染 View)符合直觉,新手易上手。
- 代码量少:小型项目中无需定义 View 接口、Presenter 等额外抽象层,直接通过 Controller 协调即可。
- 示例对比:
MVP 需额外定义// MVC 实现点击事件(Controller 直接操作 View 和 Model) public class UserController {public void onLoginClick() {User user = model.getUser();view.showUserName(user.getName()); // View 可直接访问 Model} }
IView
接口和Presenter
类,代码结构更复杂。
-
与主流框架深度集成
- Web 开发:Spring MVC、Ruby on Rails、Laravel 等框架原生支持 MVC,提供路由、数据绑定等开箱即用功能。
- 前端框架:Angular(传统 MVC 模式)、Backbone.js 直接基于 MVC 思想设计。
- 移动端:iOS 的 UIKit(
UIViewController
)遵循 MVC 范式,开发文档和社区资源丰富。
-
适合数据驱动型 UI
- 现代前端场景:在 Vue/React 等框架中,通过 数据绑定 自动同步 View 和 Model,规避了经典 MVC 的耦合问题。
例如 Vue 的响应式系统:<template><!-- View --><div>{{ user.name }}</div></template> <script> export default {data() { return { user: {} }; }, // Modelmethods: { // Controller 逻辑async fetchUser() { this.user = await api.getUser(); }} } </script>
- 现代前端场景:在 Vue/React 等框架中,通过 数据绑定 自动同步 View 和 Model,规避了经典 MVC 的耦合问题。
MVC 的典型使用场景
-
轻量级 Web 应用(CRUD 为主)
- 场景:后台管理系统、博客平台等表单操作简单的应用。
- 优势:框架(如 Spring Boot + Thymeleaf)可快速生成 Controller 和 View,无需额外抽象层。
-
框架强绑定型开发
- 场景:使用 Ruby on Rails、Django 等“全栈框架”时,遵循其内置 MVC 模式可最大化利用框架能力。
- 案例:Rails 的
rails generate scaffold
命令 1 分钟生成 MVC 三层代码。
-
需要快速迭代的项目
- 场景:创业公司 MVP(最小化可行产品)阶段或短期活动页开发。
- 优势:跳过 Presenter/View 接口设计,直接开发业务功能,速度提升 30%+。
-
现代前端应用(借助数据绑定)
- 场景:Vue/React/Angular 项目,利用响应式数据流自动更新 UI。
- 关键点:
- View 监听 Model 变化(如 Vue 的
v-model
)。 - Controller(组件方法)处理事件,修改 Model 后 View 自动刷新,无需手动同步。
- View 监听 Model 变化(如 Vue 的
何时选择 MVC 而非 MVP?
考虑维度 | 选择 MVC | 选择 MVP |
---|---|---|
项目复杂度 | 业务逻辑简单(CRUD 为主) | 业务复杂(多状态、校验、跨平台逻辑) |
团队经验 | 熟悉 Spring MVC/Rails 等框架 | 具备接口抽象设计能力 |
测试要求 | 以集成测试为主 | 需要高覆盖率的单元测试 |
技术栈 | 使用支持数据绑定的现代前端框架 | 开发桌面应用(WinForms/JavaFX)或原生安卓 |
开发速度优先级 | 极高(快速上线验证) | 可接受额外设计成本 |
MVC 的潜在缺陷及规避方案
-
Controller 臃肿
- 问题:复杂业务中 Controller 可能变成“上帝类”。
- 解决:
- 拆分 Controller 为多个细粒度类(如
UserController
、OrderController
)。 - 将业务逻辑抽离到 Service 层。
- 拆分 Controller 为多个细粒度类(如
-
View 与 Model 耦合
- 问题:经典 MVC 允许 View 直接访问 Model,导致维护困难。
- 规避:
- 在前端中使用 数据绑定框架(如 Vue),避免手动操作 DOM。
- 在后端限制 View 仅通过模板引擎访问 Model(如 Thymeleaf 的
${user.name}
)。
经典案例说明
-
Spring MVC 开发 RESTful API
@RestController // Controller public class UserController {@Autowired UserService service; // Model 业务逻辑@GetMapping("/users/{id}")public User getUser(@PathVariable int id) { // 自动序列化为 JSON(View)return service.findUserById(id);} }
- 优势:无需处理 View 渲染,JSON 序列化由框架自动完成,高效简洁。
-
Rails 快速生成博客系统
rails generate scaffold Post title:string content:text # 一键生成 MVC 代码
- 5 分钟内实现增删改查+界面,适合原型验证。
总结:MVC 的核心价值
选择 MVC 当:
✅ 开发速度至关重要(尤其验证期项目)
✅ 使用成熟的全栈框架(Rails/Django/Spring MVC)
✅ 业务以简单 CRUD 为主
✅ 前端采用响应式框架(Vue/React)选择 MVP 当:
✅ 业务逻辑高度复杂
✅ 需要跨平台复用核心逻辑
✅ 单元测试覆盖率要求 >70%
✅ 长期维护的大型桌面/企业应用
通过权衡项目规模、团队技术栈和长期维护成本,选择最匹配的架构模式。
基于您提供的 PAC(Presentation-Abstraction-Control)模式图示和 Eclipse IDE 案例,以下是该架构的深度解析:
PAC 模式核心架构
Eclipse IDE 中的 PAC 实现
层级 | 代理角色 | 表示层(Presentation) | 抽象层(Abstraction) | 控制层(Control) |
---|---|---|---|---|
顶层代理 | 工作区(Workspace) | 菜单栏/工具栏/状态栏 | IDE 全局状态(项目结构等) | 协调透视图切换和资源分配 |
中间层代理 | 透视图(Perspective) | 主题区域布局(编辑器+视图组合) | 透视图配置(保存的视图布局) | 管理视图生命周期和事件路由 |
底层代理 | 编辑器/视图(Editor/View) | 代码编辑器/文件树视图 | 文件内容/项目元数据 | 处理编辑操作与内容同步 |
PAC 模式的核心优势
-
多级抽象能力
- 问题解决:传统 MVC 在复杂系统中易出现"巨型控制器"
- PAC 方案:
- 顶层代理:全局控制(如 Eclipse 工作区资源分配)
- 中间代理:领域协调(如 Java 调试透视图管理编辑器/变量视图)
- 底层代理:具体功能实现(如 Java 编辑器语法高亮)
- 案例:在 Eclipse 中切换"调试透视图"时,顶层控制层重新分配资源,中间层重组视图布局,底层编辑器进入调试模式。
-
动态组合能力
[工作区控制层]├─ 创建 Java 透视图(中间代理) │ ├─ 控制层激活编辑器区域(底层代理)│ └─ 控制层挂载 Package Explorer 视图(底层代理)└─ 创建 Git 透视图(中间代理)└─ 控制层挂载 Commit History 视图(底层代理)
- 代理可运行时动态组合(如安装新插件添加 Git 透视图)
-
隔离性保障
- 表示层隔离:编辑器崩溃不会影响全局菜单(底层代理故障隔离)
- 数据一致性:抽象层统一管理状态(如文件修改后同步更新所有相关视图)
- 通信机制:代理间仅通过控制层消息通信(避免直接耦合)
-
多维度扩展性
扩展类型 实现方式 Eclipse 案例 垂直扩展 增加新层级代理 新增"AI 助手"全局工具栏(顶层) 水平扩展 同层级添加新代理 在透视图添加新的数据库视图(底层) 功能增强 替换代理内部组件 用 Dark Theme 替换编辑器表示层
典型使用场景
-
复合式桌面应用
- 案例:IDE(Eclipse/VSCode)、CAD 软件(AutoCAD)
- 需求:同时管理多个文档/视图/工具窗口
- PAC 价值:每个编辑窗口作为独立底层代理,透视图管理组合关系
-
可插拔架构系统
- 案例:插件化工具(Photoshop 插件、游戏模组系统)
- 实现:
// 顶层控制层注册新代理 public void registerPlugin(PluginAgent agent) {intermediateAgents.add(agent);agent.setParentControl(this); // 建立控制链 }
-
多工作流切换场景
- 案例:医疗系统(门诊/住院/药房模式切换)
- PAC 工作流:
- 顶层控制层接收模式切换指令
- 销毁当前中间代理(如门诊透视图)
- 初始化新中间代理(如住院透视图)
- 底层代理按需复用(患者信息视图保留)
-
大型工业控制软件
- 案例:SCADA 系统(电力监控/工厂控制)
- 分层实现:
- 顶层:全厂监控(表示层=拓扑图,抽象层=实时数据库)
- 中间层:车间控制(表示层=设备面板,控制层=告警路由)
- 底层:设备驱动(抽象层=PLC 通信协议)
对比 MVC/MVP 的差异优势
维度 | MVC/MVP | PAC | 优势点 |
---|---|---|---|
结构复杂度 | 平面三层结构 | 树形多级代理 | 支持超大型系统 |
功能组合 | 视图固定组合 | 运行时动态重组 | 按需加载功能模块 |
错误隔离 | 全局崩溃风险 | 代理级沙箱隔离 | 局部故障不影响整体 |
数据流 | 双向绑定 | 控制层仲裁通信 | 避免循环依赖 |
💡 关键差异:PAC 通过控制层的层级传递实现"分治",而 MVC/MVP 依赖中央控制器
PAC 的挑战与应对
-
设计复杂度高
- 应对:
- 使用代码生成工具(如 Eclipse Plug-in Development Environment)
- 采用模式参考实现(如 OSGi 规范)
- 应对:
-
消息传递开销
- 优化方案:
// 控制层消息优化(事件总线 + 过滤) eventBus.register(this, @Filter(scope="currentPerspective"));
- 优化方案:
-
调试难度
- 工具支持:
- 在控制层植入跟踪器(记录代理间消息流)
- 可视化代理拓扑(如 Eclipse 的 PDE Spy)
- 工具支持:
经典案例:Eclipse 的 PAC 实现
-
工作区(顶层代理)
- 控制层:
WorkbenchWindow
- 抽象层:
Workspace
元数据 - 表示层:菜单/状态栏/透视图容器
- 控制层:
-
Java 透视图(中间代理)
- 控制层:
JavaPerspective
- 表示层:预设的编辑器/视图区域分割
- 协调关系:
// 控制层挂载组件 public void createInitialLayout(IPageLayout layout) {layout.addEditorArea(); // 编辑器区layout.addView(PACKAGE_EXPLORER_ID, LEFT, 0.25f, editorArea); // 左侧视图layout.addView(JAVADOC_VIEW_ID, BOTTOM, 0.66f, PACKAGE_EXPLORER_ID); // 右下视图 }
- 控制层:
-
JDT 编辑器(底层代理)
- 表示层:
JavaEditor
(语法高亮/代码折叠) - 抽象层:
CompilationUnit
(AST 解析) - 控制层:
JavaEditorActionContributor
(处理保存/格式化等)
- 表示层:
何时选择 PAC?
场景 | 理由 |
---|---|
需要多工作区模式的应用 | PAC 的中间代理天然支持透视图切换 |
插件化架构系统 | 每个插件可作为独立代理动态加载 |
有局部崩溃隔离需求的关键系统 | 代理沙箱机制防止单点故障扩散 |
大规模桌面应用(>50 个功能模块) | 树形结构比平面 MVC 更易维护 |
⚠️ 慎用场景:简单 CRUD 应用、移动端 APP、纯数据 API 服务(过度设计风险高)
从混沌到结构
此类别中的模式用于避免组件和对象的混乱。特别是,它们为将高级系统任务分解为协同子任务提供支持。
分层架构
此模式有助于大型应用程序的结构化。重点在于开发一个复杂的系统,其主要特征是相互构建的复杂服务的混合。
相互依赖的高级和低级操作形成相互访问的功能,但可以细分为具有相同抽象级别的层。为了实现可重用性和/或可移植性,细分为封闭层,以便后续更改的影响仅影响该层。
问题的解决方案在于将系统水平分层堆叠,这些层封装了相同抽象级别的操作。抽象级别随着下层数量的增加而增加。信息交换通过称为服务的接口进行。较高层使用其下面一层提供的服务。不允许跨多层通信。这种分离导致各个层在特定的处理方面(如数据存储或用户交互)专业化。
这个简单的概念减少了组件之间可能的依赖关系,并提供了更高的可重用性。然而,如果一层仅仅将提供特定服务的请求传递到下一层,也可能导致开销增加。此外,诸如添加数据字段之类的更改对所有层都有垂直影响。
性能问题可以通过跳过特定层来解决,尽管这再次创建了额外的依赖关系。
管道和过滤器
ipes-and-filters架构模式基于一系列处理单元(过滤器),它们通过数据通道(管道)相互连接。每个过滤器将其结果直接转发给下一个过滤器。管道将中间结果从一个过滤器传输到下一个过滤器,这涉及到流程各个方面的解耦:
• 时间顺序上(直接或有时移)
• 传输机制/格式
• 下一个过滤器的动态确定
并行、负载共享、可选过滤器
过滤器彼此不知道对方,并且可以通过管道以任何顺序组合,从而为各个管道和过滤器提供了高度的可重用性。缺点是处理过程中出现的错误状态难以处理。
这种架构模式的典型使用示例有:
• 编译器,在每个处理步骤之后逐步处理并转发结果。典型阶段有词法分析、解析器和代码生成器。
• 数字信号处理,具有以下过滤器
图像采集、颜色校正、图像效果和压缩,它们都相互转发数字图像数据
Blackboard
几个专门的子系统提供它们的知识,以创建一个可能不完整或近似的解决方案。
下图展示了黑板模式的UML图。
黑板的元素包括:
• 一个或多个独立的知识源,从特定的角度分析问题,并将解决方案提议发送到黑板
• 一个中央黑板,管理知识源的解决方案方法或解决方案元素
• 一个控制组件,监控黑板,并在必要时控制知识源的执行
黑板模式的使用示例有图像处理、图像识别、语音识别和系统监控的软件系统。
这三种架构模式——分层架构、管道和过滤器、黑板系统——通过不同的结构化策略将混乱的系统任务转化为有序的协同子任务。以下是它们如何解决组件混乱并支持任务分解的深度分析:
1. 分层架构(Layered Architecture)
核心机制:垂直隔离与单向依赖
- 避免混乱的原理:
- 层级隔离:每层仅与直接下层通信(如业务层不可跳过数据层直接访问基础设施),杜绝了网状依赖。
- 契约标准化:层间通过接口交互(如数据层定义
UserRepository
接口),实现层内部替换不影响其他层。
- 任务分解策略:
- 垂直切分:将系统按抽象级别分解(用户交互→业务逻辑→数据存取→硬件操作)。
- 案例:Web 应用开发:
- 表现层:处理 HTTP 请求/响应
- 业务层:执行订单计算逻辑
- 数据层:调用数据库保存订单
- 基础设施层:管理数据库连接池
典型应用场景
- 企业级应用(如银行系统、ERP)
- 操作系统内核(硬件抽象层→驱动层→系统调用层)
2. 管道和过滤器(Pipes and Filters)
核心机制:数据流式处理与组件解耦
- 避免混乱的原理:
- 无状态过滤器:每个过滤器独立处理输入流,不共享状态(如日志清洗过滤器不依赖上游组件)。
- 标准化管道:数据通过统一格式传输(如 JSON 流、字节流),过滤器无需知道数据来源/去向。
- 任务分解策略:
- 水平切分:将任务拆解为串行/并行的处理阶段。
- 案例:实时日志分析系统:
日志输入 → 清洗过滤器(去噪) → 解析过滤器(提取字段) → 统计过滤器(计数错误) → 告警输出
- 新增“情感分析过滤器”可直接插入解析与统计之间,不影响其他组件。
典型应用场景
- 编译器(词法分析→语法分析→代码生成)
- ETL 数据处理系统
- Unix 命令行工具链(
grep "error" log.txt | sort | uniq -c
)
3. 黑板系统(Blackboard)
核心机制:中央数据池与知识源协作
- 避免混乱的原理:
- 共享数据池:所有状态保存在黑板(如全局内存或数据库),知识源通过事件监听获取数据变更。
- 解耦决策逻辑:控制组件根据黑板状态调度知识源(如检测到新数据时触发分析模块),而非硬编码调用链。
- 任务分解策略:
- 机会主义求解:知识源独立贡献解决方案片段,由控制组件组合最终结果。
- 案例:自动驾驶系统:
黑板数据:{ 摄像头帧, 雷达点云, GPS位置 } ├─ 知识源A:识别行人 → 写入"前方行人坐标" ├─ 知识源B:计算路径 → 写入"建议转向角度" └─ 控制组件:综合信息生成刹车指令
典型应用场景
- 人工智能系统(语音识别、医疗诊断)
- 复杂事件处理(金融欺诈检测)
- 协同编辑工具(如Google Docs的冲突解决)
模式对比:如何解决混乱并支持协作
维度 | 分层架构 | 管道和过滤器 | 黑板系统 |
---|---|---|---|
核心秩序原则 | 层级隔离+单向依赖 | 数据流标准化+无状态组件 | 中央数据池+事件驱动 |
任务分解方式 | 垂直功能分层 | 水平处理阶段链 | 知识片段独立求解 |
组件耦合度 | 低(层间接口约束) | 极低(仅依赖数据格式) | 中(依赖黑板数据结构) |
动态扩展性 | 层内可替换 | 过滤器自由插拔 | 知识源热注册 |
典型混乱症状 | 循环依赖、业务逻辑泄漏 | 状态共享污染、流程僵化 | 组件间直接调用导致蜘蛛网 |
混乱解决证明 | 修改数据库实现不影响UI层 | 调换过滤器顺序即改变流程 | 新增知识源无需修改其他模块 |
实战案例:从混乱到结构的重构
场景:医疗影像诊断系统(原始混乱状态)
- 问题:
- 显示模块直接操作数据库
- 图像处理代码与报告生成逻辑混杂
- 添加AI辅助诊断需重写核心逻辑
重构方案:
-
分层固化基础架构
- 表现层:只负责渲染诊断报告UI
- 业务层:协调影像处理与诊断决策
- 数据层:独立管理DICOM影像存储
-
管道标准化影像处理
- 新增“AI增强过滤器”插入降噪与分割之间,无需修改其他过滤器
-
黑板实现诊断协作
- 知识源A:从影像中提取纹理特征写入黑板
- 知识源B:匹配历史病例生成疑似诊断
- 控制引擎:冲突时调用知识源C进行专家规则仲裁
重构效果:
✅ 新增肝肿瘤检测模块仅需注册新知识源
✅ 更换影像存储服务只修改数据层
✅ 处理流程调整通过重组管道实现
关键结论
- 分层架构:通过垂直切割和依赖倒置,将系统稳定在金字塔结构中,适合业务边界清晰的应用。
- 管道和过滤器:以数据流为中心的线性分解,适合可拆分为顺序阶段的数据处理任务。
- 黑板系统:用中央数据池+事件触发解决不确定性问题,适合需多专家协作的复杂决策场景。
选择依据:
- 需求确定性高 → 分层/管道
- 问题求解路径未知 → 黑板系统
- 需兼顾灵活性和秩序 → 混合架构(如分层+管道)
分布式系统
此类别中的模式对经过验证的任务分配形式以及子系统相互通信的方法进行了说明。
Broker
软件行业的当前发展导致了新的应用需求。软件必须能够在分布式系统上运行,但必须不受此类系统中不断发生的结构修改的影响。
应用程序需要访问的资源可以随意分布,因此各个软件组件必须能够访问这些分布式资源。在这种情况下,透明度是关键。对于单个组件,只有使用的服务的可用性是相关的,而服务在系统中的物理提供位置无关紧要。另一个因素是系统会不断进行修改。这意味着参与流程的组件很可能在运行时发生变化。
应用程序必须通过适当的措施来弥补这一点。必须避免应用程序用户必须(或能够)参与架构细节的情况。
在分布式应用程序的架构模型中,引入了一个“代理”组件。这充当了服务器和客户端之间通信的一种交换中心。代理组件是通信的中心点。每个服务器独立地向代理注册。对于服务器要提供的每个服务,在该服务器上实现相应的服务接口,并将这些接口传达给代理。当客户端希望访问特定服务时,它们将请求发送给代理。然后,代理为相应的服务定位可用的服务器,并将客户端的请求转发给它。在处理请求后,服务器将响应发送回代理,代理将响应转发给正确的客户端。
面向服务
面向服务的架构(SOA)将软件构建块的功能接口表示为分布式、可重用、松耦合的服务,这些服务通过标准化方法进行访问。
SOA定义了三个角色:
服务提供者提供服务,并在目录服务中注册这些服务。目录服务发布由服务提供者注册的服务。服务消费者在目录中搜索特定服务,并通过目录服务响应消费者查询提供的引用调用它。然后建立到相应服务提供者的链接,并且可以使用该服务。
一般来说,服务提供低粒度的接口。当一个服务只需几次调用就能实现复杂功能时,就使用低粒度这个术语。
理想情况下,这些服务是无状态的、事务上自包含的和幂等的——换句话说,无论使用相同的输入数据调用它们多少次,它们总是提供相同的结果。
服务由契约式服务接口(用于将服务消费者与服务提供者链接起来)和服务实现组成。服务实现不属于契约的一部分,只要符合接口承诺,就可以替换。
服务与位置无关,并且可以在任何时间、从任何位置激活,只要消费者和应用程序具有适当的访问权限(“位置透明性”)。
模块化
模块化是指将软件系统合理地分解和安排为子系统和组件的术语。模块化的核心任务是将整个系统细分为组件,然后这些组件可用于映射应用程序的逻辑结构。模块化的目的是通过定义和记录清晰的边界来降低系统的复杂性。
在一个系统内结合不同的任务会增加其出错的可能性。在与执行的任务逻辑上不相关的区域中产生的不想要的副作用很难追溯和纠正。
创建单独的模块作为功能和责任区域的容器。系统耦合通过明确定义的接口进行,这些接口描述了模块之间的关系。功能适用性、完整性和简单性是模块创建中部分相互冲突的目标。
与分层架构相比,模块化允许创建单独的垂直系统和分离的责任区域。
微服务
微服务是创建和集成分布式系统的一种重要架构模式。这种方法涉及将大型系统构建为小型的功能单元。每个微服务都应该代表一个不同的功能单元。微服务是高度解耦的并且独立运行。与不应相互交流的独立系统相反,微服务可以同步和异步地相互通信。微服务是分别开发的,并且彼此独立地投入生产使用。
以下针对四大分布式系统架构模式的深度剖析,聚焦其任务分配机制与子系统通信方法,揭示如何将混乱的分布式系统转化为有序协作体系:
1. Broker模式:通信中枢驱动的任务调度
任务分配机制
- 核心策略:
- 代理中介:Broker作为唯一服务定位中心(
find_server()
),客户端只需知道服务ID而非物理位置 - 动态负载分配:Broker内置负载均衡算法(如轮询/最少连接),实现请求的自动路由
- 故障转移:当
acknowledgement()
超时,自动重定向到备用服务节点
- 代理中介:Broker作为唯一服务定位中心(
通信方法创新
- 双缓冲代理设计:
代理类型 关键方法 通信优化 Client-side Proxy pack_data()
协议封装(对象→网络字节流) Server-side Proxy unpack_data()
协议解析(字节流→服务可读对象) - Bridge组件:
transmit_message()
实现跨协议转换(如HTTP→gRPC)forward_message()
处理网络分区时的消息中继
典型场景:金融交易系统(订单路由需毫秒级定位服务实例)
2. SOA:契约驱动的服务协作
任务分配机制
- 三层角色职责:
角色 任务分配逻辑 Service Directory 服务元数据存储(版本/端点/SLA) Service Provider 按契约实现业务能力(如支付服务) Service Consumer 动态组合服务(如“下单=支付+库存”)
通信方法创新
- 标准化接入:
- 所有服务通过 WSDL/OpenAPI 描述接口
- SOAP/HTTP 统一通信协议
- 企业服务总线(ESB):
- 消息转换:XML→JSON 等格式互转
- 路由增强:基于内容的消息路由(如VIP用户请求优先处理)
解决混乱的核心设计:
-
松耦合:服务通过标准契约(WSDL/OpenAPI)交互,替换服务实现不影响消费者
-
服务复用:将“创建订单”等业务能力封装为原子服务,多个系统共享同一实现
-
组合创新:通过编排基础服务(支付+库存)快速构建新业务流程
典型案例:航空公司系统(航班查询+订票+支付服务组合销售套餐)
3. 模块化:边界驱动的能力封装
任务分配机制
flowchart TBsubgraph 用户模块A[认证] --> B[资料管理]endsubgraph 订单模块C[创建订单] --> D[支付]endB -->|事件| C <!-- 用户资料更新触发订单创建 -->
- 核心原则:
- 物理隔离:每个模块独立部署包(OSGi bundle/NPM package)
- 显式依赖:模块通过导入导出声明依赖关系(如Java的
module-info.java
)
- 任务分配优势:
- 团队自治:订单团队与用户团队并行开发
- 资源隔离:模块崩溃不影响整体系统
- 技术异构:用户模块用Java,推荐模块用Python
超越代码分包的工程哲学
核心原则:
-
高内聚低耦合:每个模块封装特定领域能力(如 用户认证模块 独立于 订单处理模块)
-
显式接口:模块通过定义良好的API交互(如 GraphQL Schema)
-
独立部署:模块可单独编译/部署(Java 9+ 的模块化系统)
在分布式场景的价值:
[电商系统模块化拆分]
├─ 用户中心模块 (独立服务)
├─ 商品目录模块 (独立服务)
├─ 订单模块 (依赖用户/商品)
└─ 推荐模块 (订阅商品变更事件)
通信方法创新
- 进程内通信:
- 发布/订阅模型(如OSGi EventAdmin)
- 服务定位器模式(模块注册服务供其他模块发现)
- 跨模块调用约束:
// 模块A暴露接口 module com.user {exports com.user.api; }// 模块B受限使用 module com.order {requires com.user; // 显式声明依赖 }
典型场景:Eclipse插件系统(各插件模块通过服务接口协作)
4. 微服务:自治驱动的分布式计算
与SOA的本质区别
维度 | SOA | 微服务 |
---|---|---|
服务粒度 | 粗粒度(业务能力级) | 细粒度(单一职责) |
通信方式 | ESB中心化通信 | 去中心化(直接HTTP/gRPC) |
数据管理 | 共享数据库常见 | 独立数据库(每个服务独享) |
部署单元 | 单体应用或大服务 | 独立进程容器化 |
微服务架构的核心组件
- 协同子任务的关键技术:
- API Gateway:统一入口处理认证、限流、路由(对应Broker的
forward_request()
) - 服务网格:通过Sidecar代理实现服务间可靠通信(强化版Bridge)
- 事件驱动:服务通过消息队列(如Kafka)异步解耦(订单创建后触发库存扣减)
- API Gateway:统一入口处理认证、限流、路由(对应Broker的
模式融合实战:电商平台架构演进
初始混乱状态
- 单体架构:用户、商品、订单代码混杂
- 数据库耦合:所有模块访问同一个MySQL
- 扩展困难:促销时订单模块崩溃拖累整个系统
基于模式的分布式重构
graph TB%% 第一层:客户端接入Web[Web Client] --> GW[API Gateway]App[App Client] --> GW%% 第二层:微服务集群(模块化拆分)GW --> User[用户服务]GW --> Product[商品服务]GW --> Order[订单服务]GW --> Payment[支付服务]%% 第三层:基础设施User --> UDB[(用户DB)]Product --> PD[(商品ES索引)]Order --> OD[(订单DB)]Payment --> PayPal[第三方支付]%% 第四层:服务协同Order -->|事件| MQ[RabbitMQ]MQ --> Inventory[库存服务] <!-- 异步解耦 -->MQ --> Audit[审计服务]%% Broker层隐式存在style Broker fill:#f9f,stroke:#333,stroke-width:0pxBroker[("隐式Broker层")]User -.注册.-> BrokerProduct -.注册.-> BrokerOrder -.发现用户服务.-> Broker
-
模块化切分
- 按业务域拆分为4个微服务(用户/商品/订单/支付)
-
Broker模式实现通信
- 每个服务内置 Client-side Proxy:处理gRPC序列化
- Service Directory:Consul实现服务注册发现(替代显式Broker组件)
-
SOA服务复用
- 支付服务暴露标准化API,被订单服务复用
-
微服务最佳实践
- 独立数据库:用户服务用MySQL,商品服务用Elasticsearch
- 异步通信:订单创建后通过消息队列通知库存服务
架构模式对比总结
模式 | 解决的核心混乱 | 任务分解策略 | 典型技术栈 |
---|---|---|---|
Broker | 服务定位难、协议异构 | 代理层封装通信复杂性 | gRPC、RabbitMQ、Nginx |
SOA | 系统孤岛、能力无法复用 | 业务能力服务化 | WebService、ESB、SOAP |
模块化 | 代码耦合、团队协作冲突 | 高内聚边界划分 | Java模块系统、OSGi |
微服务 | 单体臃肿、扩展性差 | 细粒度服务独立自治 | Docker、Kubernetes、Istio |
黄金组合建议:
- 微服务 作为顶层架构划分系统边界
- 模块化原则设计 服务内部结构
- 通过 Broker思想(服务网格)处理服务通信
- SOA理念 指导跨系统服务共享
通过分层应用这些模式,可将混乱的分布式系统转化为可扩展、高可用、易演化的有序结构。
**任务分配机制
- 任务分配四要素:
要素 实现方式 服务粒度 单一业务能力(如“支付服务”) 数据所有权 服务独享数据库(CQRS模式) 部署单元 容器化(Docker+ Kubernetes) 弹性扩缩 基于CPU/队列深度的自动伸缩
通信方法创新
- 多协议混合通信:
场景 协议 优势 服务间实时调用 gRPC 低延迟高吞吐 跨服务数据订阅 Kafka 最终一致性 外部API暴露 REST/GraphQL 客户端友好 - 服务网格赋能:
- Sidecar代理(如Envoy)自动处理:
- 熔断(失败调用阈值触发断路)
- 金丝雀发布(流量按比例切分)
- Sidecar代理(如Envoy)自动处理:
典型案例:Netflix(3000+微服务通过Zuul网关协同)
模式对比:任务分配与通信能力矩阵
模式 | 任务分配策略 | 通信方法创新点 | 适用场景 |
---|---|---|---|
Broker | 集中式服务调度 | 双缓冲代理+协议桥接 | 跨语言异构系统集成 |
SOA | 契约式服务组合 | ESB总线实现语义路由 | 企业级系统服务复用 |
模块化 | 物理边界隔离 | 进程内事件总线 | 桌面应用/单体系统改造 |
微服务 | 业务垂直切分+自治 | 服务网格+多协议混合通信 | 云原生高并发系统 |
深度洞察:模式如何解决分布式混乱
-
Broker终结“配置地狱”
- 传统痛点:客户端硬编码服务地址 → 服务迁移需全网更新配置
- 解决方案:
find_server()
动态定位服务,位置透明性提升系统弹性
-
SOA打破“信息孤岛”
- 传统痛点:烟囱式系统无法复用支付能力
- 解决方案:服务契约标准化,新业务通过组合现有服务快速上线
-
模块化根治“耦合痼疾”
- 传统痛点:修改用户认证波及订单模块
- 解决方案:模块边界强制隔离,依赖关系显式声明
-
微服务应对“流量洪峰”
- 传统痛点:单体架构扩容需整体复制(资源浪费)
- 解决方案:独立扩缩订单服务(10实例)与用户服务(2实例)
实战演进:从电商系统看模式融合(任务分配和通信方法)
阶段1:单体架构
- 问题:支付功能崩溃导致整个系统不可用
阶段2:引入Broker
- 改进点:支付服务独立部署,通过Broker路由请求
阶段3:SOA改造
- 改进点:支付服务契约标准化,被订单/库存等多系统复用
阶段4:微服务拆分
- 终极形态:
- 支付服务独立数据库保障安全
- 订单与库存通过事件最终一致
- 网关统一处理认证和限流
结论:模式选择的黄金法则
- 选Broker当:需集成遗留系统(COBOL+Java+Python)
- 选SOA当:企业存在大量可复用的核心业务能力
- 选模块化当:单体系统需渐进式改造
- 选微服务当:系统需应对百万级并发且迭代频繁
终极建议:现代云原生系统应采用 微服务+服务网格(Broker演进形态) 的组合,既保持服务自治性,又通过网格解决通信复杂性。
以下是结合布施曼思想进行的提炼和补充:
弗兰克·布施曼架构模式四大类:
-
适应性系统
- 核心问题: 如何设计能够在运行时或编译后相对容易地适应变化的系统?如何管理组件间的依赖关系以支持可替换性和灵活性?
- 关键模式:
- 依赖注入: 这是实现适应性、解耦和可测试性的基础技术。它通过将依赖关系从组件内部创建转移到外部(由“注入器”提供)来实现控制反转,使得替换实现、配置行为、进行单元测试变得非常容易。它本身是一种设计原则/技术,但支撑着许多适应性架构模式。
-
交互式系统
- 核心问题: 如何构建用户界面清晰分离于应用功能逻辑和数据的系统?如何管理用户输入、数据处理和结果显示之间的复杂交互?
- 关键模式:
- 模型-视图-控制器: 最经典的模式。将系统分为:
- 模型: 核心数据和业务逻辑(独立于UI)。
- 视图: 负责显示数据(一个模型可以有多个视图)。
- 控制器: 处理用户输入,更新模型,可能选择视图。
- 模型-视图-展示器: MVC的变体,常用于桌面和现代UI框架。主要区别:
- 视图: 更被动,通常是UI元素本身。
- 展示器: 承担了原控制器的大部分职责,并主动更新视图。视图通过接口与展示器通信。视图和模型完全解耦(由展示器中介)。
- 表示-抽象-控制: 专为需要层次化分解的复杂交互式系统设计(如具有嵌套UI组件的系统)。
- 表示: 处理特定层级的UI输出和输入。
- 抽象: 包含该层级的核心数据和功能。
- 控制: 协调表示和抽象之间的交互,并管理PAC层次结构中父节点和子节点之间的通信。
- 模型-视图-控制器: 最经典的模式。将系统分为:
-
从混沌到结构
- 核心问题: 如何为没有明显主导结构或需要清晰组织的大型复杂系统引入秩序、模块化和可管理性?如何将系统分解为可理解的、职责明确的单元?
- 关键模式:
- 分层架构: 将系统划分为一组水平层,每层提供特定抽象级别的服务,并且通常只能调用下一层(或下层)的服务(严格分层)或所有下层服务(松散分层)。经典例子:OSI网络模型、典型的企业应用分层(表现层、业务逻辑层、数据访问层)。
- 管道和过滤器: 将系统处理过程建模为一系列独立的处理步骤(过滤器),数据通过管道在这些步骤之间流动。每个过滤器独立处理输入流并产生输出流。适用于数据转换流水线(如编译器、图像处理、日志处理)。
- 黑板: 用于解决没有确定性算法的问题(如信号处理、语音识别、专家系统)。多个独立的知识源(专家)观察共享的中央数据存储(黑板),当黑板上出现它们感兴趣的数据时,它们会贡献自己的知识(更新黑板)。一个控制组件协调知识源的激活。强调机会主义和协作求解。
-
分布式系统
- 核心问题: 如何设计运行在通过网络连接的多台计算机上的系统?如何解决通信、协调、位置透明性、可伸缩性、容错性和异构性带来的挑战?
- 关键模式:
- 代理: 引入一个中间件组件(代理)来协调分布式组件之间的通信。客户端通过代理发送请求,代理负责定位服务、转发请求、返回结果。提供位置透明性、简化客户端/服务端交互。CORBA、Java RMI的注册表、消息队列的Broker节点是其实现。
- 面向服务: 一种架构范式(常通过SOA实现),将系统构建为一组松散耦合、可重用的服务。服务具有定义良好的接口,通过标准协议(通常是HTTP/SOAP/REST)进行网络通信。强调业务对齐、可组合性和互操作性。
- 微服务: SOA的一种具体化和细粒度实现风格。将单个应用程序构建为一套小型、独立部署的服务,每个服务围绕特定业务能力构建,拥有自己的数据存储,并通过轻量级机制(通常是HTTP/REST或gRPC)通信。强调自治性、技术异构性、弹性、可独立扩展和部署。
- 关于“模块化”: 您将“模块化”列在分布式系统下。这是一个需要澄清的点:
- 模块化本身是一个更基础的设计原则(高内聚、低耦合、关注点分离),贯穿于所有类别的架构模式中。分层、PAC、微服务等都体现了模块化思想。
- 在分布式上下文中: 分布式系统强烈依赖模块化来将系统分解为可以独立部署和运行的进程/服务。微服务就是模块化原则在分布式环境中的极致应用。
- 归类建议: 虽然模块化对于分布式系统至关重要,但它本身并不特属于分布式问题域。布施曼原著中并未将“模块化”作为一个独立的模式列在分布式系统大类下。它更像是实现其他模式(尤其是分层和分布式模式)的基础手段。将其理解为支撑性理念而非一个与Broker、SOA、微服务并列的模式会更准确。
总结与补充说明:
- 分类的边界: 这些分类不是绝对互斥的。一个实际系统通常会组合使用多个类别的模式。例如,一个分布式微服务系统(分布式系统)内部可能采用分层架构(从混沌到结构),其各个服务可能使用依赖注入(适应性系统)和某种MVC变体来构建Web界面(交互式系统)。
- 微服务的位置: 微服务是分布式系统模式下的一个现代且重要的子类/具体风格。将它单独列出是合理的,因为它代表了当前分布式架构的主流实践之一。
- “模块化”的理解: 将其视为一个跨领域的核心设计原则,而不是一个与其他模式并列的、特定于分布式系统的模式。
- 模式的价值: 布施曼的分类框架的价值在于它根据模式解决的核心问题域进行了组织,帮助架构师在面对特定挑战(如需要灵活适应变化、构建用户界面、组织大型复杂系统、构建分布式应用)时,能够快速定位到相关的模式集合进行选择和组合。