行為模式-命令模式(The Command Pattern)

本文大部分內容翻譯至《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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市硕并,隨后出現的幾起案子法焰,更是在濱河造成了極大的恐慌,老刑警劉巖倔毙,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異乙濒,居然都是意外死亡陕赃,警方通過查閱死者的電腦和手機卵蛉,發(fā)現死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來么库,“玉大人傻丝,你說我怎么就攤上這事∷呷澹” “怎么了葡缰?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長忱反。 經常有香客問我泛释,道長,這世上最難降的妖魔是什么温算? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任怜校,我火速辦了婚禮,結果婚禮上茄茁,老公的妹妹穿的比我還像新娘。我一直安慰自己愈犹,他們只是感情好梆掸,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布扬卷。 她就那樣靜靜地躺著,像睡著了一般酸钦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卑硫,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天徒恋,我揣著相機與錄音欢伏,去河邊找鬼入挣。 笑死硝拧,一個胖子當著我的面吹牛滋恬,可吹牛的內容都是我干的勋磕。 我是一名探鬼主播杂彭,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼习勤,長吁一口氣:“原來是場噩夢啊……” “哼眷唉!你這毒婦竟也來了驳庭?” 一聲冷哼從身側響起不皆,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體彤守,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡蔗坯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年钝诚,在試婚紗的時候發(fā)現自己被綠了拧略。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宁赤。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出臼予,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布冠摄,位于F島的核電站,受9級特大地震影響河泳,放射性物質發(fā)生泄漏沃呢。R本人自食惡果不足惜薄霜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纸兔。 院中可真熱鬧惰瓜,春花似錦、人聲如沸汉矿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洲拇。三九已至奈揍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赋续,已是汗流浹背男翰。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚕捉,地道東北人奏篙。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親秘通。 傳聞我的和親對象是個殘疾皇子为严,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內容