有两种相互作用的泛型类集是一种好的设计模式。一个简单的例子是Observable-Observer模式。可观察者向观察者发布事件,但无论观察到什么类型的事件,模式都是相同的。
我首先想到的是,首选的方法是定义两个通用协议。这应该提供最小的耦合,随着代码库的增长,这种耦合往往很好。
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
这让我有两个问题:
当我希望编译器不接受numberObservable.registerObserver(dataObserver)
时,我误解了什么?
有没有办法实现分别符合ProtocolObserver
和ProtocolObservable
的一对类?
答案 0 :(得分: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)
当被调用者尝试使用EventType
时会出现问题。 EventType
可以任何。它类似于这个功能:
func f<T>(t: T) { }
T
可以是您喜欢的任何类型 - String
,Int
,Foo
。但是你无法用它做任何事情,因为它提供零保证。要使泛型类型有用,您必须约束它(即保证它具有某些特征,例如可以添加/减去它的IntegerType
),或者将其传递给另一个类似的泛型函数不受限制的(例如将其置于通用集合中,或者调用print
或unsafeBitCast
,它将以任何类型运行)。
基本上,你的观察者都宣称“我有一系列方法,update
,你可以用你喜欢的任何类型打电话”。这不是很有用,除非你写的是像map
这样的东西或类似于数组的泛型集合,在这种情况下你不关心T
是什么。
这可能有助于消除一些困惑 - 这不按照您的想法行事:
class DataObserver : GenericObserver {
func update<NSData>(observable:GenericObservable<NSData>, event:NSData) {
print("Data Event \(event)")
}
}
这里有 not 声明DataObserver
专门接受NSData
课程。您刚刚命名了通用占位符NSData
。与命名变量NSData
类似 - 它并不意味着变量是什么,只是你所谓的变量。你可以写下这个:
class DataObserver : GenericObserver {
func update<Bork>(observable:GenericObservable<Bork>, event: Bork) {
print("Data Event \(event)")
}
}
好的,如何实现具有相关类型的可观察协议(即协议中的类型)。这是一个例子。但请注意,没有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) -> ())