Delegation說三道四


為什麼需要Delegation?

Delegation是一種「兩個不同物件的互動行為」的設計模式,為什麼需要它?原因很簡單,我們不可能用繼承(inherit)物件的方式,達成我們想要作的事,尤其是在兩種不同任務的物件類型,為了要分離清楚彼此的任務與工作,我們需要用不同的方式來達成。本文以最簡單的生活實例來理解delegation,或許可以很容易的理解它在作什麼的,與用在什麼情況的。這篇只是個起頭,或許之後還有更多針對這個議題的討論或心得文章。

對於delegation或delegate的本文中保留原字詞,就是「委託」或「代表」的意思。

各司其職

想像現在你有個「市民1999」專線的物件,主要的任務是接受市民打電話來幹譙回報他們家半夜鄰居打牌太大聲,或檢舉巷口有人亂停車的事,它的工作就是接受打電話,然後記錄下來這些事,至於聽他們抱怨或作心理輔導這些事就不是工作的範圍。那這些事情最後是該由誰去處理,像亂停車應該是警察局的工作範圍,噪音就是環保局的工作。

「市民1999」只需要把相對的資訊通知到「警察局」,或「環保局」,或者其他的相關負責單位而已。「市民1999」只是個第一線的窗口單位,它並不會是間警察局,也不是環保局、醫院或消防隊。

由實際的例子來了解後,回到電腦的程式語言中,在MVC的大架構下,所有第一線回應使用者動作的,都是View這個部份,View的工作是什麼?把外觀搞得漂漂亮亮的,然後把特效搞得炫炫的,讓使用者耳目一新愛用好用這樣。不要忘了它只是個"View",那真正處理例如按下個按鈕、點了某張圖片或某個連結,這些動作時,要作什麼事情或要跑到哪裡去的是什麼?是隱身在View之後的Controller。

小小實作

一個最簡單的實作範例如下,它對應了最前面所說的實際例子,「市民1999」專線與要委託給其他單位的互動關係:

protocol DepartmentDelegate{
    func check(numberOfPhone: String, content: String)-> Bool
}

class OneNineNineNine{
    var delegate: DepartmentDelegate?

    var numberOfPhone: String
    var content: String

    init(numberOfPhone: String, content: String){
        println("有人打電話來,電話號碼是 \(numberOfPhone)。回報內容是 \(content)" )
        self.numberOfPhone = numberOfPhone
        self.content = content
    }

    //送到負責的單位去
    func sendToResponsibleDepartment(){
        delegate?.check(self.numberOfPhone, content: self.content)
    }
}

class PoliceOffice: DepartmentDelegate{

    func check(numberOfPhone: String, content: String)-> Bool{
        //確認這個案子與情況
        println("我們有收到案子了")
        return true
    }

    //開罰單或其他後續工作
    func fine(){
    }
}

class EnvironmentalProtectionBureau: DepartmentDelegate{

    func check(numberOfPhone: String, content: String)-> Bool{
        //確認這個案子與情況
        println("我們有收到案子了")
        return true
    }

    //開罰單或其他後續工作
    func fine(){
    }
}

let taipeiPoliceOffice = PoliceOffice()
let taipeiEnvironmentalProtectionBureau = EnvironmentalProtectionBureau()

let case1 = OneNineNineNine(numberOfPhone: "02-27110110", content: "鄰居三更半夜打麻將很吵,地點是…")

case1.delegate = taipeiPoliceOffice

case1.sendToResponsibleDepartment()

let case2 = OneNineNineNine(numberOfPhone: "02-27114454", content: "有人亂丟垃圾,地點是…")

case2.delegate = taipeiEnvironmentalProtectionBureau

case2.sendToResponsibleDepartment()

這樣的實作過於簡單,你可能會想說如果由一個「部門的Superclass」當作delegate這些類的源頭有何不同,在這個例子中並沒有什麼不同(尤其是現在看起來,不知道是故意的還是搞笑的,遵守Protocol和繼承Class的語法,是一模一樣的)。但差異在更複雜的例子或更多的參與者時,就會顯露出來:

  • Swift中是只能繼承單個類,但可以遵守(conform)多個協定:單類繼承是很合理的作法,我們不太可能需要像「蜘蛛人」這種類別,從「蜘蛛」類和「人」類合體的子類,尤其是處理像「有幾支腳」的屬性問題時,會很頭大。

  • 這些委託類本質上就不一樣:警察局和環保局的類實際上不同,當有更多的任務單位出現時,例如醫院、消防隊、社福團體或其他單位時,每個單位的類別內容差異更大,不可能歸納出一個更高層的superclass

回到Protocol原本的定義,在中文的翻譯是「協定」、「協商」或「協議」。就像在真實世界中,「市民1999」專線的單位,與各支援或配合單位,只是進行一種「協議」,先開會協商好,誰負責接電話,然後確認是該交給那個單位去作真正的處理,它主要工作是讓市民的檢舉或回報事項,能發到對應的部門或單位去處理。

觀察者(Observers)

在程式中並不是所有的事情都是交給你處理,也不可能是這樣。在Cocoa的架構下,我們要處理只有我們想要的或需要的,也不是所有事件處理都是用delegation。最常使用的地方,就是當發生事件時,發出警告通知已經寫好的對應處理程式碼,這會有三種情況:

  • Should :事件(或動作)"應該"要作嗎?
  • Did :在事件(或動作)"已經"完成時
  • Will :在事件(或動作)"將要"動作時。(準備要作)

我們的App處於一個大的delegation之下,在AppDelegate.swift中可以看到以下的方法:

func applicationDidBecomeActive(application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

這個方式在何時會呼叫?在你的App"已經"變為為Active狀態時。

func applicationWillTerminate(application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }

這個方式在何時會呼叫?在你的App"將要"被終止關閉時。