/ 设计模式 / 命令模式 / Swift Swift 命令模式讲解和代码示例 命令是一种行为设计模式, 它可将请求或简单操作转换为一个对象。 此类转换让你能够延迟进行或远程执行请求, 还可将其放入队列中。 进一步了解命令模式 导航 简介 概念示例 Example Output 真实世界示例 Example Output 复杂度: 流行度: 使用示例: 命令模式在 Swift 代码中很常见。 大部分情况下, 它被用于代替包含行为的参数化 UI 元素的回调函数, 此外还被用于对任务进行排序和记录操作历史记录等。 识别方法: 命令模式可以通过抽象或接口类型 (发送者) 中的行为方法来识别, 该类型调用另一个不同的抽象或接口类型 (接收者) 实现中的方法, 该实现则是在创建时由命令模式的实现封装。 命令类通常仅限于一些特殊行为。 以下示例可在 Swift Playgrounds 上使用。 感谢 Alejandro Mohamad 创建了Playground版本。 概念示例 真实世界示例 概念示例 本例说明了命令设计模式的结构并重点回答了下面的问题: 它由哪些类组成? 这些类扮演了哪些角色? 模式中的各个元素会以何种方式相互关联? 了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。 Example.swift: 概念示例 import XCTest /// The Command interface declares a method for executing a command. protocol Command { func execute() } /// Some commands can implement simple operations on their own. class SimpleCommand: Command { private var payload: String init(_ payload: String) { self.payload = payload } func execute() { print("SimpleCommand: See, I can do simple things like printing (" + payload + ")") } } /// However, some commands can delegate more complex operations to other /// objects, called "receivers." class ComplexCommand: Command { private var receiver: Receiver /// Context data, required for launching the receiver's methods. private var a: String private var b: String /// Complex commands can accept one or several receiver objects along with /// any context data via the constructor. init(_ receiver: Receiver, _ a: String, _ b: String) { self.receiver = receiver self.a = a self.b = b } /// Commands can delegate to any methods of a receiver. func execute() { print("ComplexCommand: Complex stuff should be done by a receiver object.\n") receiver.doSomething(a) receiver.doSomethingElse(b) } } /// The Receiver classes contain some important business logic. They know how to /// perform all kinds of operations, associated with carrying out a request. In /// fact, any class may serve as a Receiver. class Receiver { func doSomething(_ a: String) { print("Receiver: Working on (" + a + ")\n") } func doSomethingElse(_ b: String) { print("Receiver: Also working on (" + b + ")\n") } } /// The Invoker is associated with one or several commands. It sends a request /// to the command. class Invoker { private var onStart: Command? private var onFinish: Command? /// Initialize commands. func setOnStart(_ command: Command) { onStart = command } func setOnFinish(_ command: Command) { onFinish = command } /// The Invoker does not depend on concrete command or receiver classes. The /// Invoker passes a request to a receiver indirectly, by executing a /// command. func doSomethingImportant() { print("Invoker: Does anybody want something done before I begin?") onStart?.execute() print("Invoker: ...doing something really important...") print("Invoker: Does anybody want something done after I finish?") onFinish?.execute() } } /// Let's see how it all comes together. class CommandConceptual: XCTestCase { func test() { /// The client code can parameterize an invoker with any commands. let invoker = Invoker() invoker.setOnStart(SimpleCommand("Say Hi!")) let receiver = Receiver() invoker.setOnFinish(ComplexCommand(receiver, "Send email", "Save report")) invoker.doSomethingImportant() } } Output.txt: 执行结果 Invoker: Does anybody want something done before I begin? SimpleCommand: See, I can do simple things like printing (Say Hi!) Invoker: ...doing something really important... Invoker: Does anybody want something done after I finish? ComplexCommand: Complex stuff should be done by a receiver object. Receiver: Working on (Send email) Receiver: Also working on (Save report) 真实世界示例 Example.swift: 真实世界示例 import Foundation import XCTest class DelayedOperation: Operation { private var delay: TimeInterval init(_ delay: TimeInterval = 0) { self.delay = delay } override var isExecuting : Bool { get { return _executing } set { willChangeValue(forKey: "isExecuting") _executing = newValue didChangeValue(forKey: "isExecuting") } } private var _executing : Bool = false override var isFinished : Bool { get { return _finished } set { willChangeValue(forKey: "isFinished") _finished = newValue didChangeValue(forKey: "isFinished") } } private var _finished : Bool = false override func start() { guard delay > 0 else { _start() return } let deadline = DispatchTime.now() + delay DispatchQueue(label: "").asyncAfter(deadline: deadline) { self._start() } } private func _start() { guard !self.isCancelled else { print("\(self): operation is canceled") self.isFinished = true return } self.isExecuting = true self.main() self.isExecuting = false self.isFinished = true } } class WindowOperation: DelayedOperation { override func main() { print("\(self): Windows are closed via HomeKit.") } override var description: String { return "WindowOperation" } } class DoorOperation: DelayedOperation { override func main() { print("\(self): Doors are closed via HomeKit.") } override var description: String { return "DoorOperation" } } class TaxiOperation: DelayedOperation { override func main() { print("\(self): Taxi is ordered via Uber") } override var description: String { return "TaxiOperation" } } class CommandRealWorld: XCTestCase { func testCommandRealWorld() { prepareTestEnvironment { let siri = SiriShortcuts.shared print("User: Hey Siri, I am leaving my home") siri.perform(.leaveHome) print("User: Hey Siri, I am leaving my work in 3 minutes") siri.perform(.leaveWork, delay: 3) /// for simplicity, we use seconds print("User: Hey Siri, I am still working") siri.cancel(.leaveWork) } } } extension CommandRealWorld { struct ExecutionTime { static let max: TimeInterval = 5 static let waiting: TimeInterval = 4 } func prepareTestEnvironment(_ execution: () -> ()) { /// This method tells Xcode to wait for async operations. Otherwise the /// main test is done immediately. let expectation = self.expectation(description: "Expectation for async operations") let deadline = DispatchTime.now() + ExecutionTime.waiting DispatchQueue.main.asyncAfter(deadline: deadline) { expectation.fulfill() } execution() wait(for: [expectation], timeout: ExecutionTime.max) } } class SiriShortcuts { static let shared = SiriShortcuts() private lazy var queue = OperationQueue() private init() {} enum Action: String { case leaveHome case leaveWork } func perform(_ action: Action, delay: TimeInterval = 0) { print("Siri: performing \(action)-action\n") switch action { case .leaveHome: add(operation: WindowOperation(delay)) add(operation: DoorOperation(delay)) case .leaveWork: add(operation: TaxiOperation(delay)) } } func cancel(_ action: Action) { print("Siri: canceling \(action)-action\n") switch action { case .leaveHome: cancelOperation(with: WindowOperation.self) cancelOperation(with: DoorOperation.self) case .leaveWork: cancelOperation(with: TaxiOperation.self) } } private func cancelOperation(with operationType: Operation.Type) { queue.operations.filter { operation in return type(of: operation) == operationType }.forEach({ $0.cancel() }) } private func add(operation: Operation) { queue.addOperation(operation) } } Output.txt: 执行结果 User: Hey Siri, I am leaving my home Siri: performing leaveHome-action User: Hey Siri, I am leaving my work in 3 minutes Siri: performing leaveWork-action User: Hey Siri, I am still working Siri: canceling leaveWork-action DoorOperation: Doors are closed via HomeKit. WindowOperation: Windows are closed via HomeKit. TaxiOperation: operation is canceled 概念示例 真实世界示例