本文大部分內容翻譯至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些許修改蕊蝗,并將代碼升級到了Swift2.0,翻譯不當之處望多包涵狮辽。
命令模式(The Command Pattern)
“行為請求者”與“行為實現者”通常呈現一種“緊耦合”一也。但在某些場合,比如要對行為進行“記錄喉脖、撤銷/重做椰苟、事務”等處理,這種無法抵御變化的緊耦合是不合適的树叽。在這種情況下舆蝴,如何將“行為請求者”與“行為實現者”解耦,將一組行為抽象為對象题诵,實現二者之間的松耦合洁仗。
示例工程
Xcode OS X Command Line Tool工程:
Calculator.swift
class Calculator {
private(set) var total = 0
func add(amount:Int) {
total += amount
}
func subtract(amount:Int) {
total -= amount
}
func multiply(amount:Int) {
total = total * amount
}
func divide(amount:Int) {
total = total / amount
}
}
Calculator類定義了一個存儲屬性total,以及改變total值的一些方法性锭。
main.swift
let calc = Calculator()
calc.add(10)
calc.multiply(4)
calc.subtract(2)
print("Total: \(calc.total)")
運行程序赠潦,輸出:
Total: 38
理解命令模式解決的問題
假如兩個請求組件同時操作同一個 Calculator 對象,每個請求組件都去調用方法去修改total的值草冈。
現在想象一下你需要支持撤銷前面的操作她奥。一個解決方法是每一個請求組件都追蹤自己的操作以便未來能撤銷瓮增。但是這么做的問題是每一個組件都不知道其他組件做了什么,這樣很可能導致撤銷出現混亂哩俭。
理解命令模式
命令模式用一個對象來提供方法調用所有可以解決撤銷等一系列問題绷跑。
命令模式的關鍵就是這個 命令對象。在它的私有實現里携茂,命令對象有接收對象的引用和如何讓接收者調用方法的說明你踩。
圖中,receiver和invocation instructions都是私有的所有請求組件并不能訪問讳苦。唯一公有的是execution method带膜。
實現命令模式
定義命令協議
實現命令模式的第一步是定義一個擁有execution 方法的協議。
Commands.swift
protocol Command {
func execute()
}
行為實現者允許行為請求者執(zhí)行方法但隱藏了receiver對象和invocation instructions鸳谜。
定義命令實現類
實現類對于swift來說可以很簡單膝藕,因為instructions可以用閉包來實現。
Commands.swift
protocol Command {
func execute()
}
class GenericCommand<T> : Command {
private var receiver: T
private var instructions: T -> Void
init(receiver:T, instructions: T -> Void) {
self.receiver = receiver
self.instructions = instructions
}
func execute() {
instructions(receiver)
}
class func createCommand(receiver:T, instuctions: T -> Void) -> Command {
return GenericCommand(receiver: receiver, instructions: instuctions)
}
}
我們定義了一個泛型實現類GenericCommand 咐扭,它有私有屬性receiver 和instructions芭挽。execute方法調用了instructions并將receiver作為參數傳遞了進去。
應用命令模式
Calculator.swift
class Calculator {
private(set) var total = 0
private var history = [Command]()
func add(amount:Int) {
addUndoCommand(Calculator.add, amount: amount)
total += amount
}
func subtract(amount:Int) {
addUndoCommand(Calculator.subtract, amount: amount)
total -= amount
}
func multiply(amount:Int) {
addUndoCommand(Calculator.multiply, amount: amount)
total = total * amount
}
func divide(amount:Int) {
addUndoCommand(Calculator.divide, amount: amount)
total = total / amount
}
private func addUndoCommand(method:Calculator -> Int -> Void, amount:Int) {
self.history.append(GenericCommand<Calculator>.createCommand(self){calc in
method(calc)(amount)
})
}
func undo() {
if self.history.count > 0 {
self.history.removeLast().execute()
// temporary measure - executing the command adds to the history
self.history.removeLast()
}
}
}
并發(fā)保護
Calculator.swift
import Foundation
class Calculator {
private(set) var total = 0
private var history = [Command]()
private var queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
private var performingUndo = false
func add(amount:Int) {
addUndoCommand(Calculator.subtract, amount: amount)
total += amount
}
func subtract(amount:Int) {
addUndoCommand(Calculator.add, amount: amount)
total -= amount
}
func multiply(amount:Int) {
addUndoCommand(Calculator.divide, amount: amount)
total = total * amount
}
func divide(amount:Int) {
addUndoCommand(Calculator.multiply, amount: amount)
total = total / amount
}
private func addUndoCommand(method:Calculator -> Int -> Void, amount:Int) {
if (!performingUndo){
dispatch_sync(queue){[weak self] in
self!.history.append(GenericCommand<Calculator>.createCommand(self!){calc in
//Currying
method(calc)(amount)
})
}
}
}
func undo() {
dispatch_sync(self.queue){[weak self] in
if self!.history.count > 0 {
self!.performingUndo = true
self!.history.removeLast().execute()
self!.performingUndo = false
}
}
}
}
我們創(chuàng)建了一個串行隊列蝗肪,用同步方法dispatch_sync提交操作命令給數組來保證 history數組不會被并發(fā)修改袜爪。同時我們定義了一個performingUndo布爾變量,用來當我們執(zhí)行撤銷命令時阻止addUndoCommand 方法調用薛闪。
使用撤銷功能
main.swift
let calc = Calculator()
calc.add(10)
calc.multiply(4)
calc.subtract(2)
print("Total: \(calc.total)")
for _ in 0 ..< 3 {
calc.undo()
print("Undo called. Total: \(calc.total)")
}
運行程序辛馆,可以看到值最后為0:
Undo called. Total: 40
Undo called. Total: 10
Undo called. Total: 0
命令模式的變形
下面將一次介紹三種變形:
1.創(chuàng)建復合命令
前面的例子中每一次只能撤銷一個命令,但是利用command協議來創(chuàng)建一個外層類實現復數命令也是很簡單的豁延。
Commands.swift
.....
class CommandWrapper : Command {
private let commands:[Command]
init(commands:[Command]) {
self.commands = commands
}
func execute() {
for command in commands {
command.execute()
}
}
}
.....
CommandWrapper類創(chuàng)建了一個存儲command對象的常量數組昙篙。接下來我們在 Calculator類中增加一個方法。
Calculator.swift
......
func getHistorySnaphot() -> Command? {
var command:Command?
dispatch_sync(queue){[weak self] in
command = CommandWrapper(commands: self!.history.reverse())
}
return command
}
.....
最后修改main.swit:
let calc = Calculator()
calc.add(10)
calc.multiply(4)
calc.subtract(2)
let snapshot = calc.getHistorySnaphot()
print("Pre-Snapshot Total: \(calc.total)")
snapshot?.execute()
print("Post-Snapshot Total: \(calc.total)")
運行程序:
Pre-Snapshot Total: 38
Post-Snapshot Total: 0
2.宏命令
命令通暢可以做成宏诱咏,這樣允許一系列的針對不同對象的操作苔可。將命令當做對象使用的話,receiver就必須傳遞給excute方法而不是初始化方法袋狞。
Commands.swift
protocol Command {
func execute(receiver:Any)
}
class CommandWrapper : Command {
private let commands:[Command]
init(commands:[Command]) {
self.commands = commands;
}
func execute(receiver:Any) {
for command in commands {
command.execute(receiver)
}
}
}
class GenericCommand<T> : Command {
private var instructions: T -> Void
init(instructions: T -> Void) {
self.instructions = instructions
}
func execute(receiver:Any) {
if let safeReceiver = receiver as? T {
instructions(safeReceiver)
} else {
fatalError("Receiver is not expected type")
}
}
class func createCommand(instuctions: T -> Void) -> Command {
return GenericCommand(instructions: instuctions)
}
}
Calculator.swift
import Foundation
class Calculator {
private(set) var total = 0
private var history = [Command]()
private var queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
func add(amount:Int) {
addMacro(Calculator.add, amount: amount)
total += amount
}
func subtract(amount:Int) {
addMacro(Calculator.subtract, amount: amount)
total -= amount
}
func multiply(amount:Int) {
addMacro(Calculator.multiply, amount: amount)
total = total * amount
}
func divide(amount:Int) {
addMacro(Calculator.divide, amount: amount)
total = total / amount
}
private func addMacro(method:Calculator -> Int -> Void, amount:Int) {
dispatch_sync(self.queue){[weak self] in
self!.history.append(GenericCommand<Calculator>.createCommand(
{ calc in method(calc)(amount) }))
}
}
func getMacroCommand() -> Command? {
var command:Command?
dispatch_sync(queue){[weak self] in
command = CommandWrapper(commands: self!.history)
}
return command
}
}
最后我們看main.swift:
let calc = Calculator()
calc.add(10)
calc.multiply(4)
calc.subtract(2)
print("Calc 1 Total: \(calc.total)")
let macro = calc.getMacroCommand()
let calc2 = Calculator()
macro?.execute(calc2)
print("Calc 2 Total: \(calc2.total)")
運行程序焚辅,得到下面結果:
Calc 1 Total: 38
Calc 2 Total: 38
3.閉包代替命令
Calculator.swift
import Foundation;
class Calculator {
private(set) var total = 0
typealias CommandClosure = (Calculator -> Void)
private var history = [CommandClosure]()
private var queue = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL)
func add(amount:Int) {
addMacro(Calculator.add, amount: amount)
total += amount
}
func subtract(amount:Int) {
addMacro(Calculator.subtract, amount: amount)
total -= amount
}
func multiply(amount:Int) {
addMacro(Calculator.multiply, amount: amount);
total = total * amount
}
func divide(amount:Int) {
addMacro(Calculator.divide, amount: amount)
total = total / amount
}
private func addMacro(method:Calculator -> Int -> Void, amount:Int) {
dispatch_sync(self.queue, {() in
self.history.append({ calc in method(calc)(amount)})
})
}
func getMacroCommand() -> (Calculator -> Void) {
var commands = [CommandClosure]();
dispatch_sync(queue, {() in
commands = self.history
})
return { calc in
if (commands.count > 0) {
for index in 0 ..< commands.count {
commands[index](calc)
}
}
}
}
}
接著我們看main.swift:
let calc = Calculator()
calc.add(10)
calc.multiply(4)
calc.subtract(2)
print("Calc 1 Total: \(calc.total)")
let macro = calc.getMacroCommand()
let calc2 = Calculator()
macro(calc2)
print("Calc 2 Total: \(calc2.total)")
運行程序:
Calc 1 Total: 38
Calc 2 Total: 38