
스위프트로 작성된 커맨드
커맨드는 요청 또는 간단한 작업을 객체로 변환하는 행동 디자인 패턴입니다.
이러한 변환은 명령의 지연 또는 원격 실행, 명령 기록 저장 등을 허용합니다.
복잡도:
인기도:
사용 예시들: 커맨드 패턴은 스위프트 코드에서 매우 일반적입니다. 대부분의 경우 작업으로 UI 요소를 매개 변수화하기 위한 콜백의 대안으로 사용되며 작업 대기, 작업 기록 추적 등에도 사용됩니다.
식별: 커맨드 패턴은 다음과 같은 특징이 있습니다. 추상/인터페이스 유형(발신자)의 행동 메서드들이 있으며 이러한 메서드들은 다른 추상/인터페이스 유형(수신자)의 구현에서 메서드를 호출하며 이 메서드는 생성되는 동안 커맨드 구현으로 캡슐화되었습니다. 또 커맨드 클래스는 일반적으로 특정 작업만 수행할 수 있습니다.
개념적인 예시
이 예시는 커맨드 디자인 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 스위프트 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
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, @unchecked Sendable { 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, @unchecked Sendable { override func main() { print("\(self): Windows are closed via HomeKit.") } override var description: String { return "WindowOperation" } } class DoorOperation: DelayedOperation, @unchecked Sendable { override func main() { print("\(self): Doors are closed via HomeKit.") } override var description: String { return "DoorOperation" } } class TaxiOperation: DelayedOperation, @unchecked Sendable { 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