模式在软件的设计和开发中是一个重要的工具。在软件开发的许多领域都存在模式——例如,设计模式、架构模式、分析模式、软件组织模式和教学模式。
架构模式的分类是按照弗兰克·布施曼(Frank Buschmann)的四类系统进行的。其基本概念是以模式所解决的问题作为分类的基础。
此类别中的模式支持应用程序的扩展以及它们对不断发展的技术和不断变化的功能需求的适应。
在面向对象设计中,由于需要创建一个抽象接口的具体实例,经常会出现问题。
• 谁管理所使用实例的生命周期?
• 谁决定在运行时最终实例化哪个具体的类?
为此,此模式提供了一个独立的构建块,即:装配器。
装配器在运行时确定如何解决上述问题。装配器将依赖对象的特定实例的引用传递过去。它可以被视为一种“通用工厂”。
它首先检查 ServiceUser 对于必要的依赖项(Service),并通过元信息生成或确定提供所需服务的 ServiceImplementation。然后,它将此服务实现“注入”到 ServiceUser 中,从而将类与其依赖项解耦。
已有的用于依赖注入的 Java 实现有:
• JEE 6:上下文和依赖注入(JSR - 299)
• Spring框架
核心组件解析
ServiceUser
(服务使用者 / 客户端类):
Service
(通过 <> Service
表示依赖关系,通常是关联或聚合关系,图中用 -->>
表示)。ServiceUser
不负责创建或查找它所依赖的 Service
具体实现。这就是解耦的关键。<
(服务接口):
ServiceUser
所需的服务契约(方法签名)。ServiceUser
所依赖的服务的抽象类型。ServiceImplementation
(服务实现类):
Service
接口的一个具体实现类。Service
接口定义的功能的实际逻辑。◁|--
(空心三角箭头+虚线) 或类似的实现(Realization) 关系线指向 <> Service
,表示它“实现了”该接口 (
)。Assembler
(装配器 / 注入器 / DI容器):
ServiceUser
所需的 Service
接口应该由哪个具体实现类(这里是 ServiceImplementation
) 来提供。ServiceImplementation
(以及它可能依赖的其他对象)。ServiceImplementation
实例(作为 Service
接口的实现)“注入” 到 ServiceUser
对象中。注入通常通过构造函数、Setter方法或字段赋值完成。依赖注入工作流程:
Assembler
识别出 ServiceUser
依赖于 Service
接口。Assembler
根据配置或规则,找到 Service
接口对应的具体实现类是 ServiceImplementation
。Assembler
创建 ServiceImplementation
的实例。Assembler
将 ServiceImplementation
实例传递给 ServiceUser
。这可以通过:
ServiceUser
时,将 ServiceImplementation
实例作为参数传给 ServiceUser
的构造函数。ServiceUser
后,调用 ServiceUser
的 setter 方法(例如 setService(Service s)
)将 ServiceImplementation
实例传入。ServiceUser
内部持有 Service
接口的字段。ServiceUser
现在持有了一个实现了 Service
接口的对象(ServiceImplementation
),可以调用其方法来使用所需的服务功能,而无需关心该对象是如何创建或从哪里来的。关键优势 (解耦):
ServiceUser
与 ServiceImplementation
解耦: ServiceUser
只依赖于稳定的接口 Service
,完全不知道 ServiceImplementation
的存在。ServiceImplementation
可以被替换成任何其他实现了 Service
接口的类(例如 AnotherServiceImplementation
或用于测试的 MockServiceImplementation
),而不需要修改 ServiceUser
的代码。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 {
@Override
public void sendMessage(String message, String recipient) {
System.out.println("发送邮件给 " + recipient + ": " + message);
// 实际邮件发送逻辑...
}
}
class SMSService implements MessageService {
@Override
public 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("测试消息", "[email protected]");
}
// 模拟服务实现 (不需要实际发送消息)
static class MockMessageService implements MessageService {
@Override
public void sendMessage(String message, String recipient) {
System.out.println("[模拟] 消息已发送给 " + recipient);
// 这里可以添加断言验证逻辑
}
}
}
依赖抽象(DIP原则)
// 客户端依赖抽象接口
class NotificationService {
private final MessageService messageService; // 依赖抽象
}
依赖注入(三种方式)
public NotificationService(MessageService service) {
this.messageService = service;
}
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 {
@Bean
public MessageService messageService() {
return new SMSService(); // 或EmailService
}
@Bean
public NotificationService notificationService(MessageService service) {
return new NotificationService(service); // 自动注入
}
}
// 客户端类
@Service
public class NotificationService {
@Autowired // 自动注入依赖
private MessageService messageService;
}
这个示例完整展示了:
通过这种模式,当需要更换消息服务(如从短信切换到邮件)时,只需修改装配器的配置,而无需修改NotificationService
的任何代码。
交互系统模式支持交互式软件系统的结构化。
将应用程序移植到不同的平台不应导致整个应用程序的重构。在这里,目标是简单地更改或扩展以及重用各个组件。
用户界面经常变化。相同的信息必须以不同的方式在不同的窗口中提供,导致所需框架的复杂性。不同的用户组需要不同的布局或格式。在模型的一致视图和过度更新导致的性能问题之间很难取得平衡。
为了解决这个问题,用户界面被分为三个责任区域。模型封装了通常稳定的业务逻辑及其数据。视图组件提供模型的视图。控制器处理用户事件,执行相应的业务逻辑,并触发视图组件的更新。
这种方法的一个很好的例子是电子表格程序,它为相同的数据模型提供了详细的表格视图和易于理解的图表视图。
模型 - 视图 - 展示器(MVP)是一种面向用户界面应用的交互式软件系统的架构模式。它基于模型 - 视图 - 控制器模式,侧重于严格分离用户界面和业务逻辑(分离视图和模型)。此模式背后的原则是将应用程序分解为三个组件:
• 模型包含业务逻辑和视图所需的所有数据。它仅由展示器控制,既不知道视图也不知道展示器。
• 视图代表表示层,设计极其简单。它绝对不包含控制逻辑,仅负责展示和接收用户输入。
• 展示器将视图链接到模型,并控制各层之间的逻辑流。它收集所有应用程序用例,接受来自视图的用户输入,调用模型中的方法,并在视图中设置数据。
由于表示层结构简单,视图可以被其他视图替换。通过这种方式,系统可以进行修改以便在不同平台上使用,并且与 MVC 相比,易于测试。自 2004 年马丁·福勒(Martin Fowler)定义以来,MVP 已被广泛用于客户端的开发。
应用程序功能的增加也会增加用户界面的复杂性。对于复杂的用户界面,不同的功能区域可能会相互混杂,从而降低了可维护性。此外,简单的分解(如在 MVC 模式中)会导致,当所有用户事件都由单个控制器处理时,响应时间不令人满意等问题。
在这种模式中,用户界面的结构被分解为分层协作的“代理”。中级使用基本功能,然后为用户界面的各个底层元素提供功能。每个代理都由一个控制器、抽象和视图组成。控制器是代理与层次结构中上下层代理的接口,并控制其责任区域。抽象将完整模型的部分适配为一个本地模型,该本地模型仅包含本地视图所需的元素。这种严格的分层分离能够在用户界面内实现处理操作的并行化,特别是在只有完整模型的部分可用的情况下。
Eclipse IDE 就是一个很好的例子:工作区提供了菜单栏、工具栏、工作区和状态栏。透视图将这些作为嵌入的操作元素提供给每个透视图的主题区域使用。这些包括边距视图和编辑器窗口的区域,然后由特定的内容编辑器和视图用它们自己的菜单项填充。
架构 | 交互关系 | 依赖方向 |
---|---|---|
MVC | View ↔ Controller ↔ Model 允许 View 直接访问 Model |
双向依赖(View 可能依赖 Model) |
MVP | View ↔ Presenter ↔ Model View 与 Model 完全隔离 |
单向依赖(View 仅依赖 Presenter 接口) |
您的图示清晰体现了这一关键差异:MVP 中 View 和 Model 无直接连接,Presenter 居中协调。
彻底解耦 View 与业务逻辑
IUserView.showUserName(String name)
)。可测试性显著提升
// 测试 Presenter 是否正确处理了用户登录
@Test
void testLoginSuccess() {
MockUserView mockView = new MockUserView(); // 模拟 View
UserPresenter presenter = new UserPresenter(mockView, new UserModel());
presenter.onLoginClick("user", "pass123");
assertTrue(mockView.showSuccessWasCalled); // 验证 View 接口被正确调用
}
避免“巨型 Controller”问题
UserProfilePresenter
、OrderHistoryPresenter
)。桌面应用(Windows Forms, Java Swing)
需要高可测试性的应用
多平台共享业务逻辑
遗留系统重构
维度 | MVC | MVP |
---|---|---|
View 职责 | 可能包含业务逻辑 | 仅负责 UI 渲染 |
可测试性 | 较低(依赖 UI 框架) | 高(Presenter 可独立测试) |
维护成本 | 复杂业务中易失控 | 逻辑集中,更易维护 |
适用平台 | Web 框架、简单应用 | 桌面应用、跨平台核心逻辑 |
建议选择 MVP 当:
✅ 应用业务逻辑复杂
✅ 需要高频单元测试
✅ 计划支持多平台 UI
✅ 长期维护性至关重要
与 MVP 相比,MVC(Model-View-Controller) 的核心优势在于轻量、开发效率高、与主流框架深度集成,尤其适合业务逻辑简单的快速开发场景。以下是具体分析:
更低的认知与实现成本
// MVC 实现点击事件(Controller 直接操作 View 和 Model)
public class UserController {
public void onLoginClick() {
User user = model.getUser();
view.showUserName(user.getName()); // View 可直接访问 Model
}
}
MVP 需额外定义 IView
接口和 Presenter
类,代码结构更复杂。与主流框架深度集成
UIViewController
)遵循 MVC 范式,开发文档和社区资源丰富。适合数据驱动型 UI
{{ user.name }}
轻量级 Web 应用(CRUD 为主)
框架强绑定型开发
rails generate scaffold
命令 1 分钟生成 MVC 三层代码。需要快速迭代的项目
现代前端应用(借助数据绑定)
v-model
)。考虑维度 | 选择 MVC | 选择 MVP |
---|---|---|
项目复杂度 | 业务逻辑简单(CRUD 为主) | 业务复杂(多状态、校验、跨平台逻辑) |
团队经验 | 熟悉 Spring MVC/Rails 等框架 | 具备接口抽象设计能力 |
测试要求 | 以集成测试为主 | 需要高覆盖率的单元测试 |
技术栈 | 使用支持数据绑定的现代前端框架 | 开发桌面应用(WinForms/JavaFX)或原生安卓 |
开发速度优先级 | 极高(快速上线验证) | 可接受额外设计成本 |
Controller 臃肿
UserController
、OrderController
)。View 与 Model 耦合
${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);
}
}
Rails 快速生成博客系统
rails generate scaffold Post title:string content:text # 一键生成 MVC 代码
选择 MVC 当:
✅ 开发速度至关重要(尤其验证期项目)
✅ 使用成熟的全栈框架(Rails/Django/Spring MVC)
✅ 业务以简单 CRUD 为主
✅ 前端采用响应式框架(Vue/React)选择 MVP 当:
✅ 业务逻辑高度复杂
✅ 需要跨平台复用核心逻辑
✅ 单元测试覆盖率要求 >70%
✅ 长期维护的大型桌面/企业应用
通过权衡项目规模、团队技术栈和长期维护成本,选择最匹配的架构模式。
基于您提供的 PAC(Presentation-Abstraction-Control)模式图示和 Eclipse IDE 案例,以下是该架构的深度解析:
层级 | 代理角色 | 表示层(Presentation) | 抽象层(Abstraction) | 控制层(Control) |
---|---|---|---|---|
顶层代理 | 工作区(Workspace) | 菜单栏/工具栏/状态栏 | IDE 全局状态(项目结构等) | 协调透视图切换和资源分配 |
中间层代理 | 透视图(Perspective) | 主题区域布局(编辑器+视图组合) | 透视图配置(保存的视图布局) | 管理视图生命周期和事件路由 |
底层代理 | 编辑器/视图(Editor/View) | 代码编辑器/文件树视图 | 文件内容/项目元数据 | 处理编辑操作与内容同步 |
多级抽象能力
动态组合能力
[工作区控制层]
├─ 创建 Java 透视图(中间代理)
│ ├─ 控制层激活编辑器区域(底层代理)
│ └─ 控制层挂载 Package Explorer 视图(底层代理)
└─ 创建 Git 透视图(中间代理)
└─ 控制层挂载 Commit History 视图(底层代理)
隔离性保障
多维度扩展性
扩展类型 | 实现方式 | Eclipse 案例 |
---|---|---|
垂直扩展 | 增加新层级代理 | 新增"AI 助手"全局工具栏(顶层) |
水平扩展 | 同层级添加新代理 | 在透视图添加新的数据库视图(底层) |
功能增强 | 替换代理内部组件 | 用 Dark Theme 替换编辑器表示层 |
复合式桌面应用
可插拔架构系统
// 顶层控制层注册新代理
public void registerPlugin(PluginAgent agent) {
intermediateAgents.add(agent);
agent.setParentControl(this); // 建立控制链
}
多工作流切换场景
大型工业控制软件
维度 | MVC/MVP | PAC | 优势点 |
---|---|---|---|
结构复杂度 | 平面三层结构 | 树形多级代理 | 支持超大型系统 |
功能组合 | 视图固定组合 | 运行时动态重组 | 按需加载功能模块 |
错误隔离 | 全局崩溃风险 | 代理级沙箱隔离 | 局部故障不影响整体 |
数据流 | 双向绑定 | 控制层仲裁通信 | 避免循环依赖 |
关键差异:PAC 通过控制层的层级传递实现"分治",而 MVC/MVP 依赖中央控制器
设计复杂度高
消息传递开销
// 控制层消息优化(事件总线 + 过滤)
eventBus.register(this, @Filter(scope="currentPerspective"));
调试难度
工作区(顶层代理)
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 的中间代理天然支持透视图切换 |
插件化架构系统 | 每个插件可作为独立代理动态加载 |
有局部崩溃隔离需求的关键系统 | 代理沙箱机制防止单点故障扩散 |
大规模桌面应用(>50 个功能模块) | 树形结构比平面 MVC 更易维护 |
⚠️ 慎用场景:简单 CRUD 应用、移动端 APP、纯数据 API 服务(过度设计风险高)
此类别中的模式用于避免组件和对象的混乱。特别是,它们为将高级系统任务分解为协同子任务提供支持。
此模式有助于大型应用程序的结构化。重点在于开发一个复杂的系统,其主要特征是相互构建的复杂服务的混合。
相互依赖的高级和低级操作形成相互访问的功能,但可以细分为具有相同抽象级别的层。为了实现可重用性和/或可移植性,细分为封闭层,以便后续更改的影响仅影响该层。
问题的解决方案在于将系统水平分层堆叠,这些层封装了相同抽象级别的操作。抽象级别随着下层数量的增加而增加。信息交换通过称为服务的接口进行。较高层使用其下面一层提供的服务。不允许跨多层通信。这种分离导致各个层在特定的处理方面(如数据存储或用户交互)专业化。
这个简单的概念减少了组件之间可能的依赖关系,并提供了更高的可重用性。然而,如果一层仅仅将提供特定服务的请求传递到下一层,也可能导致开销增加。此外,诸如添加数据字段之类的更改对所有层都有垂直影响。
性能问题可以通过跳过特定层来解决,尽管这再次创建了额外的依赖关系。
ipes-and-filters架构模式基于一系列处理单元(过滤器),它们通过数据通道(管道)相互连接。每个过滤器将其结果直接转发给下一个过滤器。管道将中间结果从一个过滤器传输到下一个过滤器,这涉及到流程各个方面的解耦:
• 时间顺序上(直接或有时移)
• 传输机制/格式
• 下一个过滤器的动态确定
并行、负载共享、可选过滤器
过滤器彼此不知道对方,并且可以通过管道以任何顺序组合,从而为各个管道和过滤器提供了高度的可重用性。缺点是处理过程中出现的错误状态难以处理。
这种架构模式的典型使用示例有:
• 编译器,在每个处理步骤之后逐步处理并转发结果。典型阶段有词法分析、解析器和代码生成器。
• 数字信号处理,具有以下过滤器
图像采集、颜色校正、图像效果和压缩,它们都相互转发数字图像数据
几个专门的子系统提供它们的知识,以创建一个可能不完整或近似的解决方案。
下图展示了黑板模式的UML图。
黑板的元素包括:
• 一个或多个独立的知识源,从特定的角度分析问题,并将解决方案提议发送到黑板
• 一个中央黑板,管理知识源的解决方案方法或解决方案元素
• 一个控制组件,监控黑板,并在必要时控制知识源的执行
黑板模式的使用示例有图像处理、图像识别、语音识别和系统监控的软件系统。
这三种架构模式——分层架构、管道和过滤器、黑板系统——通过不同的结构化策略将混乱的系统任务转化为有序的协同子任务。以下是它们如何解决组件混乱并支持任务分解的深度分析:
UserRepository
接口),实现层内部替换不影响其他层。日志输入 → 清洗过滤器(去噪) → 解析过滤器(提取字段) → 统计过滤器(计数错误) → 告警输出
grep "error" log.txt | sort | uniq -c
)黑板数据:{ 摄像头帧, 雷达点云, GPS位置 }
├─ 知识源A:识别行人 → 写入"前方行人坐标"
├─ 知识源B:计算路径 → 写入"建议转向角度"
└─ 控制组件:综合信息生成刹车指令
维度 | 分层架构 | 管道和过滤器 | 黑板系统 |
---|---|---|---|
核心秩序原则 | 层级隔离+单向依赖 | 数据流标准化+无状态组件 | 中央数据池+事件驱动 |
任务分解方式 | 垂直功能分层 | 水平处理阶段链 | 知识片段独立求解 |
组件耦合度 | 低(层间接口约束) | 极低(仅依赖数据格式) | 中(依赖黑板数据结构) |
动态扩展性 | 层内可替换 | 过滤器自由插拔 | 知识源热注册 |
典型混乱症状 | 循环依赖、业务逻辑泄漏 | 状态共享污染、流程僵化 | 组件间直接调用导致蜘蛛网 |
混乱解决证明 | 修改数据库实现不影响UI层 | 调换过滤器顺序即改变流程 | 新增知识源无需修改其他模块 |
分层固化基础架构
管道标准化影像处理
黑板实现诊断协作
重构效果:
✅ 新增肝肿瘤检测模块仅需注册新知识源
✅ 更换影像存储服务只修改数据层
✅ 处理流程调整通过重组管道实现
选择依据:
- 需求确定性高 → 分层/管道
- 问题求解路径未知 → 黑板系统
- 需兼顾灵活性和秩序 → 混合架构(如分层+管道)
此类别中的模式对经过验证的任务分配形式以及子系统相互通信的方法进行了说明。
软件行业的当前发展导致了新的应用需求。软件必须能够在分布式系统上运行,但必须不受此类系统中不断发生的结构修改的影响。
应用程序需要访问的资源可以随意分布,因此各个软件组件必须能够访问这些分布式资源。在这种情况下,透明度是关键。对于单个组件,只有使用的服务的可用性是相关的,而服务在系统中的物理提供位置无关紧要。另一个因素是系统会不断进行修改。这意味着参与流程的组件很可能在运行时发生变化。
应用程序必须通过适当的措施来弥补这一点。必须避免应用程序用户必须(或能够)参与架构细节的情况。
在分布式应用程序的架构模型中,引入了一个“代理”组件。这充当了服务器和客户端之间通信的一种交换中心。代理组件是通信的中心点。每个服务器独立地向代理注册。对于服务器要提供的每个服务,在该服务器上实现相应的服务接口,并将这些接口传达给代理。当客户端希望访问特定服务时,它们将请求发送给代理。然后,代理为相应的服务定位可用的服务器,并将客户端的请求转发给它。在处理请求后,服务器将响应发送回代理,代理将响应转发给正确的客户端。
面向服务的架构(SOA)将软件构建块的功能接口表示为分布式、可重用、松耦合的服务,这些服务通过标准化方法进行访问。
SOA定义了三个角色:
服务提供者提供服务,并在目录服务中注册这些服务。目录服务发布由服务提供者注册的服务。服务消费者在目录中搜索特定服务,并通过目录服务响应消费者查询提供的引用调用它。然后建立到相应服务提供者的链接,并且可以使用该服务。
一般来说,服务提供低粒度的接口。当一个服务只需几次调用就能实现复杂功能时,就使用低粒度这个术语。
理想情况下,这些服务是无状态的、事务上自包含的和幂等的——换句话说,无论使用相同的输入数据调用它们多少次,它们总是提供相同的结果。
服务由契约式服务接口(用于将服务消费者与服务提供者链接起来)和服务实现组成。服务实现不属于契约的一部分,只要符合接口承诺,就可以替换。
服务与位置无关,并且可以在任何时间、从任何位置激活,只要消费者和应用程序具有适当的访问权限(“位置透明性”)。
模块化是指将软件系统合理地分解和安排为子系统和组件的术语。模块化的核心任务是将整个系统细分为组件,然后这些组件可用于映射应用程序的逻辑结构。模块化的目的是通过定义和记录清晰的边界来降低系统的复杂性。
在一个系统内结合不同的任务会增加其出错的可能性。在与执行的任务逻辑上不相关的区域中产生的不想要的副作用很难追溯和纠正。
创建单独的模块作为功能和责任区域的容器。系统耦合通过明确定义的接口进行,这些接口描述了模块之间的关系。功能适用性、完整性和简单性是模块创建中部分相互冲突的目标。
与分层架构相比,模块化允许创建单独的垂直系统和分离的责任区域。
微服务是创建和集成分布式系统的一种重要架构模式。这种方法涉及将大型系统构建为小型的功能单元。每个微服务都应该代表一个不同的功能单元。微服务是高度解耦的并且独立运行。与不应相互交流的独立系统相反,微服务可以同步和异步地相互通信。微服务是分别开发的,并且彼此独立地投入生产使用。
以下针对四大分布式系统架构模式的深度剖析,聚焦其任务分配机制与子系统通信方法,揭示如何将混乱的分布式系统转化为有序协作体系:
find_server()
),客户端只需知道服务ID而非物理位置acknowledgement()
超时,自动重定向到备用服务节点代理类型 | 关键方法 | 通信优化 |
---|---|---|
Client-side Proxy | pack_data() |
协议封装(对象→网络字节流) |
Server-side Proxy | unpack_data() |
协议解析(字节流→服务可读对象) |
transmit_message()
实现跨协议转换(如HTTP→gRPC)forward_message()
处理网络分区时的消息中继典型场景:金融交易系统(订单路由需毫秒级定位服务实例)
角色 | 任务分配逻辑 |
---|---|
Service Directory | 服务元数据存储(版本/端点/SLA) |
Service Provider | 按契约实现业务能力(如支付服务) |
Service Consumer | 动态组合服务(如“下单=支付+库存”) |
松耦合:服务通过标准契约(WSDL/OpenAPI)交互,替换服务实现不影响消费者
服务复用:将“创建订单”等业务能力封装为原子服务,多个系统共享同一实现
组合创新:通过编排基础服务(支付+库存)快速构建新业务流程
典型案例:航空公司系统(航班查询+订票+支付服务组合销售套餐)
flowchart TB
subgraph 用户模块
A[认证] --> B[资料管理]
end
subgraph 订单模块
C[创建订单] --> D[支付]
end
B -->|事件| C
module-info.java
)超越代码分包的工程哲学
核心原则:
高内聚低耦合:每个模块封装特定领域能力(如 用户认证模块 独立于 订单处理模块)
显式接口:模块通过定义良好的API交互(如 GraphQL Schema)
独立部署:模块可单独编译/部署(Java 9+ 的模块化系统)
在分布式场景的价值:
[电商系统模块化拆分]
├─ 用户中心模块 (独立服务)
├─ 商品目录模块 (独立服务)
├─ 订单模块 (依赖用户/商品)
└─ 推荐模块 (订阅商品变更事件)
// 模块A暴露接口
module com.user {
exports com.user.api;
}
// 模块B受限使用
module com.order {
requires com.user; // 显式声明依赖
}
典型场景:Eclipse插件系统(各插件模块通过服务接口协作)
维度 | SOA | 微服务 |
---|---|---|
服务粒度 | 粗粒度(业务能力级) | 细粒度(单一职责) |
通信方式 | ESB中心化通信 | 去中心化(直接HTTP/gRPC) |
数据管理 | 共享数据库常见 | 独立数据库(每个服务独享) |
部署单元 | 单体应用或大服务 | 独立进程容器化 |
forward_request()
)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:0px
Broker[("隐式Broker层")]
User -.注册.-> Broker
Product -.注册.-> Broker
Order -.发现用户服务.-> Broker
模块化切分
Broker模式实现通信
SOA服务复用
微服务最佳实践
模式 | 解决的核心混乱 | 任务分解策略 | 典型技术栈 |
---|---|---|---|
Broker | 服务定位难、协议异构 | 代理层封装通信复杂性 | gRPC、RabbitMQ、Nginx |
SOA | 系统孤岛、能力无法复用 | 业务能力服务化 | WebService、ESB、SOAP |
模块化 | 代码耦合、团队协作冲突 | 高内聚边界划分 | Java模块系统、OSGi |
微服务 | 单体臃肿、扩展性差 | 细粒度服务独立自治 | Docker、Kubernetes、Istio |
黄金组合建议:
- 微服务 作为顶层架构划分系统边界
- 模块化原则设计 服务内部结构
- 通过 Broker思想(服务网格)处理服务通信
- SOA理念 指导跨系统服务共享
通过分层应用这些模式,可将混乱的分布式系统转化为可扩展、高可用、易演化的有序结构。
要素 | 实现方式 |
---|---|
服务粒度 | 单一业务能力(如“支付服务”) |
数据所有权 | 服务独享数据库(CQRS模式) |
部署单元 | 容器化(Docker+ Kubernetes) |
弹性扩缩 | 基于CPU/队列深度的自动伸缩 |
场景 | 协议 | 优势 |
---|---|---|
服务间实时调用 | gRPC | 低延迟高吞吐 |
跨服务数据订阅 | Kafka | 最终一致性 |
外部API暴露 | REST/GraphQL | 客户端友好 |
典型案例:Netflix(3000+微服务通过Zuul网关协同)
模式 | 任务分配策略 | 通信方法创新点 | 适用场景 |
---|---|---|---|
Broker | 集中式服务调度 | 双缓冲代理+协议桥接 | 跨语言异构系统集成 |
SOA | 契约式服务组合 | ESB总线实现语义路由 | 企业级系统服务复用 |
模块化 | 物理边界隔离 | 进程内事件总线 | 桌面应用/单体系统改造 |
微服务 | 业务垂直切分+自治 | 服务网格+多协议混合通信 | 云原生高并发系统 |
Broker终结“配置地狱”
find_server()
动态定位服务,位置透明性提升系统弹性SOA打破“信息孤岛”
模块化根治“耦合痼疾”
微服务应对“流量洪峰”
阶段1:单体架构
阶段2:引入Broker
阶段3:SOA改造
阶段4:微服务拆分
终极建议:现代云原生系统应采用 微服务+服务网格(Broker演进形态) 的组合,既保持服务自治性,又通过网格解决通信复杂性。
以下是结合布施曼思想进行的提炼和补充:
弗兰克·布施曼架构模式四大类:
适应性系统
交互式系统
从混沌到结构
分布式系统
总结与补充说明: