2015-07-12 172 views
2

有兩種相互作用的泛型類集合是一種很好的設計模式。一個簡單的例子是Observable-Observer模式。可觀察的事件向觀察者發佈事件,但是無論觀察到什麼類型的事件,模式都是相同的。Swift中協議和泛型的限制

我的第一個想法是,首選的方法是定義兩個通用協議。這應該提供最小的耦合,隨着代碼庫的增長,這種耦合會變得很好。

protocol ProtocolObserver { 
    typealias EventType 
    func update<O:ProtocolObservable where O.EventType == EventType>(observable:O, event:EventType) -> Void 
} 

protocol ProtocolObservable { 
    typealias EventType 
    func registerObserver<O:ProtocolObserver where O.EventType == EventType>(observer:O) -> Bool 
    func unregisterObserver<O:ProtocolObserver where O.EventType == EventType>(observer:O) -> Void 
} 

試圖定義實現上述協議的類竟然是一個受傷的世界。我沒有找到任何方法來做到這一點。

然而,實現泛型基類將是一個可接受的解決方案。

protocol GenericObserver { 
    func update<EventType>(observable:GenericObservable<EventType>, event:EventType); 
} 

class GenericObservable<EventType> { 
    private var observers:[GenericObserver] = [] 
    func registerObserver(observer:GenericObserver) -> Bool { 
     // Code to avoid registering the same observer twice 
     observers.append(observer) 
     return true 
    } 
    func unregisterObserver(observer:GenericObserver) -> Void { 
     // Code to remove the observer if present in observers 
    } 
    func notifyObservers(event:EventType) -> Void { 
     for observer in observers { 
      observer.update(self, event: event) 
     } 
    } 
} 

這次沒有定義一些實現協議的類的問題。將它們添加到通用觀察器的實例並未顯示出我期望的行爲。

let numberObservable = GenericObservable<NSNumber>() 

class NumberObserver : GenericObserver { 
    func update<NSNumber>(observable:GenericObservable<NSNumber>, event:NSNumber) { 
     print("Number Event \(event)") 
    } 
} 

let numberObserver = NumberObserver() 

numberObservable.registerObserver(numberObserver) 

class DataObserver : GenericObserver { 
    func update<NSData>(observable:GenericObservable<NSData>, event:NSData) { 
     print("Data Event \(event)") 
    } 
} 

let dataObserver = DataObserver() 

numberObservable.registerObserver(dataObserver) 

numberObservable.notifyObservers(NSNumber(int: 42)) 

我預計numberObservable.registerObserver(dataObserver)會導致編譯錯誤。相反,它高興地打印輸出

Number Event 42 
Data Event 42 

這一切給我留下了兩個問題:

  1. 我有什麼誤解,當我期望編譯器不接受numberObservable.registerObserver(dataObserver)

  2. 有沒有一種方法可以分別實現一對符合ProtocolObserverProtocolObservable的類?

回答

4

您的問題1和2實際上是強相關的。

在開始之前,我應該指出,當你有一流的功能時,observable/observer模式幾乎完全是多餘的。而不是強制回調接口,你可以提供一個閉包。我將在問題2的答案中顯示此內容。

首先,1.您遇到的問題是類型擦除。你的基類是隻在您已經定義registerObserver的地方,它看起來像這樣:

class GenericObservable<EventType> { 
    private var observers:[GenericObserver] = [] 
    func registerObserver(observer:GenericObserver) -> Bool { 
     // Code to avoid registering the same observer twice 
     observers.append(observer) 
     return true 
    } 
    //... 
} 

也就是說,它會採取和存儲協議參考任何類型。對於什麼類型沒有限制,它可以是任何東西。例如,您可以通知的Int

extension Int: GenericObserver { 
    func update<EventType>(observable:GenericObservable<EventType>, event:EventType) { 
     print("Integer \(self)") 
    } 
} 

numberObservable.registerObserver(2) 

問題會時被調用者嘗試使用EventTypeEventType可能是任何東西。它類似於這樣的功能:

func f<T>(t: T) { } 

T可以是任何你喜歡的類型 - 一個String,一個Int,一個Foo。但你無法做任何事情,因爲它提供了零擔保。爲了使泛型有用,你必須限制它(即保證它具有某些特徵,例如它可以被加/減),或者將它傳遞給另一個不受約束的泛型函數(比如把它放在一個通用的集合中,或者撥打printunsafeBitCast,它將以任何類型操作)。

基本上,你的觀察者都聲明「我有一套方法,update,你可以用任何你喜歡的類型打電話」。這不是很有用,除非你寫的東西像map或像數組這樣的泛型集合,在這種情況下你不在乎T是什麼。

這可能有助於澄清一些混亂 - 這並沒有做你認爲它的作用:

class DataObserver : GenericObserver { 
    func update<NSData>(observable:GenericObservable<NSData>, event:NSData) { 
     print("Data Event \(event)") 
    } 
} 

在這裏,你有沒有宣佈特別需要一個NSData類。您剛剛命名了通用佔位符NSData。類似於命名變量NSData - 這並不意味着變量是什麼,只是這就是你所說的。你可以寫這樣的:

class DataObserver : GenericObserver { 
    func update<Bork>(observable:GenericObservable<Bork>, event: Bork) { 
     print("Data Event \(event)") 
    } 
} 

確定,所以如何實現與相關類型可觀察到的協議(即在協議的typealias)。這是一個例子。但請注意,沒有Observer協議。相反,Observable將採用任何接收適當事件類型的函數。

protocol Observable { 
    typealias EventType 
    func register(f: EventType->()) 
} 
// No need for an "Observer" protocol 

現在,讓我們實現這一點,固定EventType是一個Int

struct FiresIntEvents { 
    var observers: [Int->()] = [] 

    // note, this sets the EventType typealias 
    // implicitly via the types of the argument 
    mutating func register(f: Int->()) { 
     observers.append(f) 
    } 

    func notifyObservers(i: Int) { 
     for f in observers { 
      f(i) 
     } 
    } 
} 

var observable = FiresIntEvents() 

現在,如果我們想通過一個類來觀察,我們可以:

class IntReceiverClass { 
    func receiveInt(i: Int) { 
     print("Class received \(i)") 
    } 
} 

let intReceiver = IntReceiverClass() 
// hook up the observing class to observe 
observable.register(intReceiver.receiveInt) 

但我們還可以註冊任意功能:

observable.register { print("Unowned closure received \($0)") } 
在同一個接收器個

或註冊兩個不同的功能:

extension IntReceiverClass { 
    func recieveIntAgain(i: Int) { 
     print("Class recevied \(i) slightly differently") 
    } 
} 

observable.register(intReceiver.recieveIntAgain) 

現在,當你觸發事件:

observable.notifyObservers(42) 

將得到以下的輸出:

Class received 42 
Unowned closure received 42 
Class recevied 42 slightly differently 

但與此技術,如果您嘗試註冊錯誤事件類型的函數,則會出現編譯錯誤:

observable.register(IntReceiverClass.receiveString) 
// error: cannot invoke 'register' with an argument list of type '(IntReceiverClass -> (String) ->()) 
+0

非常感謝你提供了這個非常有趣和有啓發性的答案。是的,舊的C++程序員確實相信他以與實例化C++模板相同的方式引入了兩種不同的類型。 只是爲了澄清我的第二個問題的一點。 Swift不允許我使用'Observable'協議作爲變量或函數參數。我是否正確地說,這使得我無法使用一對協議,例如我在示例中使用的'ProtocolObserver'和'ProtocolObservable'? –

+0

是的,沒有部分專業化是使用Swift訪問C++開發人員的東西。請參閱[這個答案](http://stackoverflow.com/a/30483738/3925941)關於你不能使用'ProtocolObserver'作爲參數的原因的更多信息,因爲它有一個關聯的類型。 –

+0

@Airspeed你可以請解釋一下,'observable.register(intReceiver.receiveInt)'工作如何?我相信這個方法接受一個函數(嚴格地說,指向代碼段中的某個內存的指針),而不需要任何關聯的數據。我錯了嗎? 'observable'如何捕獲'intReceiver'來稍後調用一個方法? – slashdot