La Commande est un patron de conception comportemental qui convertit des demandes ou des traitements simples en objets.
Cette conversion permet de différer ou d’exécuter à distance des commandes, de gérer un historique de commandes, etc.
Exemple conceptuel
Dans cet exemple, nous allons voir la structure du patron de conception Commande et répondre aux questions suivantes :
Que contiennent les classes ?
Quels rôles jouent-elles ?
Comment les éléments du patron sont-ils reliés ?
Après avoir étudié la structure du patron, vous pourrez plus facilement comprendre l’exemple suivant qui est basé sur un cas d’utilisation réel en Swift.
Example.swift: Exemple conceptuel
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: Résultat de l’exécution
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)
Analogie du monde réel
Example.swift: Analogie du monde réel
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: Résultat de l’exécution
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