第6章:协议在模块化架构中的应用
6.4 实战案例:构建一个模块化的任务管理系统
在前几节中,我们探讨了模块化架构的核心概念、使用协议定义模块接口以及模块间的通信机制。本节将通过一个实战案例,将这些知识整合应用,设计并实现一个模块化的任务管理系统。该系统将展示如何利用协议化编程(POP)实现模块解耦、通信和测试,体现模块化设计的实际价值。
案例背景
我们要开发一个任务管理系统,支持以下功能:
- 添加和管理任务。
- 记录任务日志。
- 通知用户任务状态更新。
- 支持不同存储方式(如内存、文件)。
目标是设计一个模块化系统,确保各模块独立、可替换,并易于测试。
系统模块划分
- 任务管理模块:负责任务的创建和状态管理。
- 存储模块:处理任务数据的持久化。
- 日志模块:记录任务操作日志。
- 通知模块:发送任务状态通知。
- 协调模块:整合各模块,协调业务逻辑。
第一步:定义模块接口
使用协议定义各模块的接口:
// 任务管理模块接口
protocol TaskManager {
func addTask(title: String)
func completeTask(id: String)
}
// 存储模块接口
protocol TaskStore {
func saveTask(id: String, title: String, completed: Bool)
func loadTasks() -> [Task]
func updateTask(id: String, completed: Bool)
}
struct Task {
let id: String
let title: String
var completed: Bool
}
// 日志模块接口
protocol Logger {
func log(_ message: String)
}
// 通知模块接口
protocol Notifier {
func notify(event: String, data: [String: Any])
}
- 每个协议定义了模块的核心职责,保持单一性。
第二步:实现模块
为每个模块提供默认实现:
class InMemoryTaskStore: TaskStore {
private var tasks: [String: Task] = [:]
func saveTask(id: String, title: String, completed: Bool) {
tasks[id] = Task(id: id, title: title, completed: completed)
}
func loadTasks() -> [Task] {
return Array(tasks.values)
}
func updateTask(id: String, completed: Bool) {
if var task = tasks[id] {
task.completed = completed
tasks[id] = task
}
}
}
struct ConsoleLogger: Logger {
func log(_ message: String) {
print("[Console] \(message)")
}
}
struct PrintNotifier: Notifier {
func notify(event: String, data: [String: Any]) {
print("Notification: \(event) - \(data)")
}
}
class TaskManagerImpl: TaskManager {
private let store: TaskStore
private let logger: Logger
private let notifier: Notifier
init(store: TaskStore, logger: Logger, notifier: Notifier) {
self.store = store
self.logger = logger
self.notifier = notifier
}
func addTask(title: String) {
let id = UUID().uuidString
store.saveTask(id: id, title: title, completed: false)
logger.log("Added task: \(title) (ID: \(id))")
notifier.notify(event: "taskAdded", data: ["id": id, "title": title])
}
func completeTask(id: String) {
store.updateTask(id: id, completed: true)
logger.log("Completed task ID: \(id)")
notifier.notify(event: "taskCompleted", data: ["id": id])
}
}
TaskManagerImpl依赖注入所有模块,协调任务操作。
第三步:协调模块
创建一个协调器整合系统:
class TaskSystemCoordinator {
private let taskManager: TaskManager
init(taskManager: TaskManager) {
self.taskManager = taskManager
}
func start() {
taskManager.addTask(title: "Write report")
taskManager.addTask(title: "Review code")
taskManager.completeTask(id: loadFirstTaskId())
}
private func loadFirstTaskId() -> String {
// 假设从存储中获取第一个任务 ID
return "some-id" // 简化处理,实际应从 store 获取
}
}
let store = InMemoryTaskStore()
let logger = ConsoleLogger()
let notifier = PrintNotifier()
let taskManager = TaskManagerImpl(store: store, logger: logger, notifier: notifier)
let system = TaskSystemCoordinator(taskManager: taskManager)
system.start()
// 输出示例:
// [Console] Added task: Write report (ID: uuid1)
// Notification: taskAdded - ["id": "uuid1", "title": "Write report"]
// [Console] Added task: Review code (ID: uuid2)
// Notification: taskAdded - ["id": "uuid2", "title": "Review code"]
// [Console] Completed task ID: some-id
// Notification: taskCompleted - ["id": "some-id"]
TaskSystemCoordinator通过注入TaskManager启动系统。
第四步:扩展模块
添加文件存储支持:
struct FileTaskStore: TaskStore {
func saveTask(id: String, title: String, completed: Bool) {
print("Saved to file: \(id) - \(title)")
}
func loadTasks() -> [Task] {
return [Task(id: "file1", title: "File task", completed: false)]
}
func updateTask(id: String, completed: Bool) {
print("Updated task \(id) in file to completed: \(completed)")
}
}
let fileSystem = TaskSystemCoordinator(
taskManager: TaskManagerImpl(store: FileTaskStore(), logger: logger, notifier: notifier)
)
fileSystem.start()
// 输出示例:
// Saved to file: uuid3 - Write report
// Notification: taskAdded - ["id": "uuid3", "title": "Write report"]
- 无需修改核心逻辑即可切换存储模块。
第五步:测试模块
为 TaskManager 添加单元测试:
struct MockTaskStore: TaskStore {
var tasks: [String: Task] = [:]
func saveTask(id: String, title: String, completed: Bool) { tasks[id] = Task(id: id, title: title, completed: completed) }
func loadTasks() -> [Task] { Array(tasks.values) }
func updateTask(id: String, completed: Bool) { tasks[id]?.completed = completed }
}
struct MockLogger: Logger {
var logs: [String] = []
func log(_ message: String) { logs.append(message) }
}
struct MockNotifier: Notifier {
var notifications: [(String, [String: Any])] = []
func notify(event: String, data: [String: Any]) { notifications.append((event, data)) }
}
import XCTest
class TaskManagerTests: XCTestCase {
func testAddTask() {
let store = MockTaskStore()
let logger = MockLogger()
let notifier = MockNotifier()
let manager = TaskManagerImpl(store: store, logger: logger, notifier: notifier)
manager.addTask(title: "Test task")
let taskId = store.tasks.keys.first!
XCTAssertEqual(store.tasks[taskId]?.title, "Test task")
XCTAssertEqual(logger.logs, ["Added task: Test task (ID: \(taskId))"])
XCTAssertEqual(notifier.notifications[0].0, "taskAdded")
XCTAssertEqual(notifier.notifications[0].1["title"] as? String, "Test task")
}
}
- Mock 对象验证了任务添加逻辑和通信行为。
案例分析
- 模块解耦:各模块通过协议交互,独立实现。
- 通信机制:使用依赖注入和通知模式,确保灵活性。
- 可测试性:Mock 实现隔离模块,便于单元测试。
- 扩展性:新存储或通知方式只需遵循协议即可。
注意事项
- ID 处理:实际应用中需完善 ID 生成和获取逻辑。
- 异步支持:可为存储或通知添加异步接口。
- 依赖管理:大型系统可使用 DI 容器简化注入。
小结
通过构建一个模块化的任务管理系统,我们展示了协议在定义接口、实现通信和支持测试中的作用。这一案例体现了模块化架构的完整流程,为开发者提供了可复用的设计模板。下一章将探讨协议在跨平台开发中的应用,进一步扩展其潜力。
