我正在做一个虚构的练习,以尝试实现类型擦除的容器。
import Foundation
protocol MoverType {
func move()
}
extension MoverType {
func move() { print("\(type(of: self)) is moving") }
}
class Slithering: MoverType {}
class Walking: MoverType {}
class Trotting: MoverType {}
protocol Animal {
associatedtype Mover: MoverType
var mover: Mover { get }
}
class Snake: Animal {
let mover = Slithering()
}
class Dog: Animal {
let mover = Trotting()
}
class Human: Animal {
let mover = Walking()
}
class AnyAnimal: Animal { // ERROR: Type 'AnyAnimal' does not conform to protocol 'Animal'
var _mover: () -> Mover
init<A: Animal>(animal: A) where Mover == A.Mover {
_mover = {
return animal.mover
}
}
// ERROR HERE: "Reference to invalid associated type "Mover" of of type "AnyAnimal"
var mover: Mover { fatalError() }
}
let d = AnyAnimal(animal: Dog())
let s = AnyAnimal(animal: Snake())
let animals = [d, s] // Array<AnyAnimal>
for a in animals {
a.mover.move()
}
我故意不希望我的AnyAnimal
容器成为AnyAnimal<T>
容器。因为,我希望能够在Animal
中存储许多Array<AnyAnimal>
实例。
但是,如您在上面的代码中看到的那样,编译器在抱怨AnyAnimal
类。据我了解,Mover
的协议要求将由where
初始化程序中的通用AnyAnimal
子句解决。
请帮助我了解缺失的内容。 (或者,甚至有可能首先创建一个非通用的类型擦除包装器?)
答案 0 :(得分:2)
您的代码无法编译,因为需要在编译时通过为Mover
协议提供具体的实现来解析关联的类型。
您可以做的是同时清除MoverType
协议:
struct AnyMover: MoverType {
private let mover: MoverType
init(_ mover: MoverType) {
self.mover = mover
}
func move() {
mover.move()
}
}
class AnyAnimal: Animal {
let mover: AnyMover
init<A: Animal>(animal: A) {
mover = AnyMover(animal.mover)
}
}
答案 1 :(得分:1)
我同意Cristik关于添加AnyMover类型橡皮擦的意见。从字面上看,这就是您要说的方法。但是,如果您走这条路,通常会设计错误的协议。例如,虽然我知道这是捏造的,但这是协议设计错误的一个很好的例子。动物协议几乎可以肯定是:
protocol Animal {
func move()
}
然后您的associatedtype
和相关问题消失了。而且您的界面更有意义。动物可以移动。这不是“与移动者”。我希望这种类型的每次使用都与您的示例animal.mover.move()
几乎完全相同。也就是说,mover
是调用者不必关心的实现细节。
在[Animal]
循环中您还能做什么 ?您如何编写使用.mover
并没有调用.move
的通用代码?没有其他方法。
我知道这是捏造的,但是这种情况恰恰是在许多实际情况下出现的,因此您应该警惕。当您发现自己喜欢类型擦除器时,特别是如果您开始达到两个级别的类型擦除器时,您需要询问您做错了什么。并非总是如此,但是在大多数情况下,对于您而言,有一个更好的解决方案。
顺便说一句,这里的另一种方法是保留Movers,但只要让它们成为协议即可。呼叫者是否真的想知道履带式和滑行式之间的区别?难道不是要隐藏整个目标吗?如果是这样,您可以这样:
protocol Animal {
var mover: MoverType { get }
}
同样,所有问题都消失了。
无论哪种方法,如果有move()
可用,您仍然可以自动实现mover
方法。例如,您可以通过以下方式设计协议:
// Something that moves animals
protocol Mover {
func move(animal: Animal)
}
// Something that has a mover
protocol MoverProviding {
var mover: Mover { get }
}
// And of course Animals. They might be MoverProviding. They might not.
protocol Animal {
func move()
}
// But if they *are* MoverProviding, we can use that.
extension Animal where Self: MoverProviding {
func move() {
mover.move(animal: self)
}
}
键入associatedtype
时,通常应该想到“该协议就是向其他类型添加额外的算法(方法和函数)。”如果协议的重点是允许异构收集,那么您可能走错了路。类型擦除器有时是有用且重要的,但是如果您觉得自己非常需要它们(尤其是由于异构集合而特别需要它们),则可能存在设计问题。