我有遵循以下常规设计的代码:
protocol DispatchType {} class DispatchType1: DispatchType {} class DispatchType2: DispatchType {} func doBar<D:DispatchType>(value:D) { print("general function called") } func doBar(value:DispatchType1) { print("DispatchType1 called") } func doBar(value:DispatchType2) { print("DispatchType2 called") }
其中,在现实中DispatchType其实是一个后端存储。这些doBar功能是优化方法,取决于正确的存储类型。如果我这样做,一切正常:
DispatchType
doBar
let d1 = DispatchType1() let d2 = DispatchType2() doBar(value: d1) // "DispatchType1 called" doBar(value: d2) // "DispatchType2 called"
但是,如果我做一个函数调用doBar:
func test<D:DispatchType>(value:D) { doBar(value: value) }
我尝试了类似的调用模式,我得到:
test(value: d1) // "general function called" test(value: d2) // "general function called"
Swift似乎应该能够处理这种事情,因为它应该能够在编译时确定类型约束。为了进行快速测试,我还尝试编写doBar为:
func doBar<D:DispatchType>(value:D) where D:DispatchType1 { print("DispatchType1 called") } func doBar<D:DispatchType>(value:D) where D:DispatchType2 { print("DispatchType2 called") }
但得到相同的结果。
任何想法,如果这是正确的Swift行为,如果是,这是解决此行为的好方法?
编辑1 :为什么我尝试避免使用协议的示例。假设我有代码(大大简化了我的实际代码):
protocol Storage { // ... } class Tensor<S:Storage> { // ... }
对于Tensor该类,我有一组基本操作,可以在上执行Tensor。但是,操作本身将根据存储更改其行为。目前,我通过以下方式完成此任务:
Tensor
func dot<S:Storage>(_ lhs:Tensor<S>, _ rhs:Tensor<S>) -> Tensor<S> { ... }
虽然我可以将它们放在Tensor类中并使用扩展名:
extension Tensor where S:CBlasStorage { func dot(_ tensor:Tensor<S>) -> Tensor<S> { // ... } }
这有一些我不喜欢的副作用:
我认为dot(lhs, rhs)是更可取的lhs.dot(rhs)。可以编写便利功能来解决此问题,但这会产生大量的代码。
dot(lhs, rhs)
lhs.dot(rhs)
这将导致Tensor该类变得单一。我真的更喜欢让它包含所需的最少代码,并通过辅助功能扩展其功能。
与(2)相关,这意味着任何想要添加新功能的人都必须接触基类,我认为这是不好的设计。
编辑2 :一种替代方法是,如果对所有内容都使用约束,则事情会正常进行:
func test<D:DispatchType>(value:D) where D:DispatchType1 { doBar(value: value) } func test<D:DispatchType>(value:D) where D:DispatchType2 { doBar(value: value) }
将导致正确doBar的被调用。这也不是理想的,因为这将导致编写很多额外的代码,但至少让我保留了当前的设计。
编辑3 :我看到文档显示了static泛型使用关键字。这至少有助于点(1):
static
class Tensor<S:Storage> { // ... static func cos(_ tensor:Tensor<S>) -> Tensor<S> { // ... } }
允许您编写:
let result = Tensor.cos(value)
它支持运算符重载:
let result = value1 + value2
它确实具有required的附加冗长性Tensor。使用以下方法可以使效果更好:
typealias T<S:Storage> = Tensor<S>
这确实是正确的行为,因为重载解析是在编译时进行的(在运行时进行这将是非常昂贵的操作)。因此,从内部看test(value:),编译器唯一了解的value就是它符合某种类型DispatchType-因此它可以分派给它的 唯一 重载是func doBar<D : DispatchType>(value: D)。
test(value:)
value
func doBar<D : DispatchType>(value: D)
如果泛型函数始终由编译器进行特殊处理,则情况将有所不同,因为这样的特殊实现test(value:)将知道的具体类型,value从而能够选择适当的重载。但是,泛型函数的专业化目前仅是一种优化(因为没有内联,它会给您的代码带来很大的膨胀),因此这不会改变观察到的行为。
为了实现多态性,一种解决方案是通过添加协议见证者表(请参阅有关WWDC的精彩演讲),方法是添加doBar()为协议需求,并在符合协议的各个类中实现它的专门实现,通用实现是协议扩展的一部分。
doBar()
这将允许动态分配doBar(),从而允许从中调用它并调用test(value:)正确的实现。
protocol DispatchType { func doBar() } extension DispatchType { func doBar() { print("general function called") } } class DispatchType1: DispatchType { func doBar() { print("DispatchType1 called") } } class DispatchType2: DispatchType { func doBar() { print("DispatchType2 called") } } func test<D : DispatchType>(value: D) { value.doBar() } let d1 = DispatchType1() let d2 = DispatchType2() test(value: d1) // "DispatchType1 called" test(value: d2) // "DispatchType2 called"