具有结构/协议的快速多态闭包调度



我有一种情况,我想向服务注册一个参数或无参数闭包。总是有一个可用的参数,但为了简洁起见,我希望能够注册没有arg闭包,然后在这种情况下只发送没有可用参数的闭包。来自于一个强大的OO和动态类型背景,我们喜欢多态调度和类继承树,并让类型自行决定,我可以将以下内容放在一起:

class AbstractAction<T> {
    func publish(value:T) {
        fatalError("you should override this")
    }
}
class NullaryAction<T>: AbstractAction<T> {
    var closure:() -> ()
    override func publish(_:T) {
        closure()
    }
    init(closure:()->()) {
        self.closure = closure
    }
}
class UnaryAction<T>: AbstractAction<T> {
    var closure:(T) -> ()
    override func publish(value:T) {
        closure(value)
    }
    init(closure:(T)->()) {
        self.closure = closure
    }
}
var action:AbstractAction = UnaryAction<Int>(closure: { print("($0)") })
action.publish(42)
action = NullaryAction<Int>(closure: { print("something happened") } )
action.publish(42)

因此,我在控制台中看到42something happened。太棒了

但我想探索一下用struct和/或enum来实现这一点。价值语义风靡一时。enum方法相对简单,我认为:

enum Action<T> {
    case Nullary( ()->() )
    case Unary( (T)->() )
    func publish(value:T) {
        switch self {
        case .Nullary(let closure):
            closure()
        case .Unary(let closure):
            closure(value)
        }
    }
}
var action = Action.Unary({ (arg:Int) -> () in print("(arg)") })
action.publish(42)
action = Action<Int>.Unary( { print("shorthand too ($0)") } )
action.publish(42)
action = Action<Int>.Nullary({ print("something happened") })
action.publish(42)

为了实现struct方法,我认为我应该使用一个协议来捕获publish(value:T)的公共接口。但这就是事情变得令人困惑的地方,因为协议显然不能与泛型混合?我试过了:

struct NullaryAction<T> {
    typealias ValueType = T
    var closure:() -> ()
}
struct UnaryAction<T> {
    typealias ValueType = T
    var closure:(T) -> ()
}
protocol Action {
    typealias ValueType
    func publish(value:ValueType)
}
extension NullaryAction: Action {
    func publish(_:ValueType) {
        self.closure()
    }
}
extension UnaryAction: Action {
    func publish(value:ValueType) {
        self.closure(value)
    }
}
var action:Action = UnaryAction(closure: { (arg:Int) -> () in print("(arg)") })
action.publish(42)
action = UnaryAction<Int>(closure: { print("shorthand too ($0)") } )
action.publish(42)
action = NullaryAction<Int>(closure:{ print("something happened") })
action.publish(42)

这只会在底部产生很多错误。我曾尝试将扩展作为泛型(例如extension NullaryAction<T>:Action),但它告诉我T是未使用的,尽管我已经将typealias表达式放在了扩展中。

用结构/协议可以做到这一点吗?我对enum解决方案很满意,但对无法用结构/协议方法实现它感到失望。

根据您想要将结构转换为它们的协议(通过使用var action: Action = UnaryAction {...})的事实判断,我假设您不需要publish方法在调用它时具有正确的签名。

换句话说,通过用typealias声明Action协议,编译器期望为结构的每个实例专门化publish方法。

这意味着你有两个选择:

  1. 移除类型铸造:

示例:

var action /*removed the :Action type casting */ = UnaryAction<Int>(closure: { (arg:Int) -> () in print("(arg)") })
action.publish(42)
action = UnaryAction<Int>(closure: { print("shorthand too ($0)") } )
action.publish(42)
var anotherAction = NullaryAction<Int>(closure:{ print("something happened") }) //need to use another variable for this last one
anotherAction.publish(42)

此解决方案使publish方法也具有与结构体相同的签名。如果您的结构专门用于处理Ints,那么您就有了.publish(value: Int)

  1. 使协议非通用

示例:

protocol Action {
    func publish(value:Any)
}
struct NullaryAction<T>: Action {
    let closure: () -> ()
    init(closure: () -> ()) {
        self.closure = closure
    }
    func publish(value:Any) {
        self.closure()
    }
}
struct UnaryAction<T>: Action {
    let closure: (T) -> ()
    init(closure: (T) -> ()) {
        self.closure = closure
    }
    func publish(value:Any) {
        self.closure(value as! T) //Need to type cast here
    }
}
var action: Action = UnaryAction<Int>(closure: { (arg:Int) -> () in print("(arg)") })
action.publish(42)
action = UnaryAction<Int>(closure: { print("shorthand too ($0)") } )
action.publish(42)
action = NullaryAction<Int>(closure:{ print("something happened") })
action.publish(42)

此解决方案允许您继续进行类型转换,但您的publish方法都将具有相同的签名(.publish(value: Any))。您还需要在执行闭包时考虑到这一点。

最新更新