Self 类型的存储变量(尤其是子类化时)
Stored variable of Self type (especially when subclassing)
class LinkedNode<T> {
var value: T
var next: Self?
required init(_ value: T) {
self.value = value
}
}
class DoublyNode<T>: LinkedNode<T> {
weak var previous: DoublyNode?
override var next: Self? {
didSet { next.previous = self }
}
}
我希望它能像这样编译。但事实并非如此。
Stored property cannot have covariant 'Self' type
我必须重写代码才能使其编译和工作:
class LinkedNode<T> {
var value: T
var next: LinkedNode?
required init(_ value: T) {
self.value = value
}
}
class DoublyNode<T>: LinkedNode<T> {
weak var previous: DoublyNode?
override var next: LinkedNode<T>? {
didSet { (next as! Self).previous = self }
}
}
在后面的代码中,每次引用 next
属性 后,我都必须手动将其转换为 DoublyNode(当我使用 DoublyNode 时),因为它始终具有 LinkedNode 类型。
它易于管理,但令人讨厌且丑陋。
Swift 的子 classes 不允许属性被协变覆盖(即,具有作为 super
类型的子类型的类型为此 属性).
这是一个更简单的例子,没有泛型:
class Animal {}
class Bird: Animal {}
class AnimalFeeder {
var animalToFeed: Animal
init() { fatalError("not relevant to the example") }
}
class BirdFeeder: AnimalFeeder {
override var animalToFeed: Bird // error: cannot override mutable property 'animalToFeed' of type 'Animal' with covariant type 'Bird'
}
你有很多示例,除了 Self
从这个类比中同时扮演动物和动物喂食者的角色。
这个数据模型从根本上被打破了
它患有square-rectangle problem。除非 next
和 prev
指针被紧密封装,并且仅通过受控函数公开,否则此 class 会破坏 LSP。
如果您将 DoublyNode<T>
传递给期望 LinkedNode<T>
的算法,该算法可以对链表的结构进行操作,仅将其视为 singly-linked,并忽略prev
参考文献。这样做,它从根本上打破了 doubly-linked 列表的不变性,并使其无效。
didSet
观察器有帮助,但它不会在初始化时触发(因此对象可能从一开始就是错误的)。我认为这一切都可以通过添加更多封装来解决,并使这个对象对其属性更具权威性,这样它们就可以确保它们保持有效。但我认为这是一个有争议的问题,因为还有更深层次的问题:
“这两个 classes 共享 3 个字段中的 2 个”不是继承的良好动机。说真的,你从这份遗产中还能得到什么?我认为拥有两个单独的链接节点 classes 会获得更多价值,每个链接节点由一个 struct SinglyLinkedList<T>
和一个 struct DoublyLinkedList<T>
拥有,它们符合 Collection
和 [=22] =]分别(定义前向和后向迭代)
让我来说明为什么 Swift 不允许这样做。如果它允许你像那样使用 Self
,理论上你可以这样做:
let doublyNode = DoublyNode(1)
let linkedNode: LinkedNode<Int> = doublyNode // this assignment should work, right?
linkedNode.next = LinkedNode(2) // "linkedNode" is of type LinkedNode, so Self is LinkedNode, so I can do this assignment, right?
现在发生了什么? DoublyNode
中的 next
的 didSet
被调用,它尝试访问 LinkedNode(2)
的 previous
。问题是,LinkedNode
甚至没有 previous
属性!因此,让你这样使用Self
是不安全的。
我认为 DoublyNode
根本不应该继承自 LinkedNode
。 Alexander 的回答很好地解释了这一点,即这违反了 LSP。将这两个 类 联系起来可以做的一件事是使用协议:
protocol LinkedNodeProtocol {
associatedtype Data
var value: Data { get set }
var next: Self? { get set }
init(_ value: Data)
}
final class LinkedNode<Data>: LinkedNodeProtocol {
var value: Data
var next: LinkedNode?
init(_ value: Data) {
self.value = value
}
}
final class DoublyNode<Data>: LinkedNodeProtocol {
var value: Data
weak var previous: DoublyNode?
var next: DoublyNode? {
didSet { next?.previous = self }
}
init(_ value: Data) {
self.value = value
}
}
class LinkedNode<T> {
var value: T
var next: Self?
required init(_ value: T) {
self.value = value
}
}
class DoublyNode<T>: LinkedNode<T> {
weak var previous: DoublyNode?
override var next: Self? {
didSet { next.previous = self }
}
}
我希望它能像这样编译。但事实并非如此。
Stored property cannot have covariant 'Self' type
我必须重写代码才能使其编译和工作:
class LinkedNode<T> {
var value: T
var next: LinkedNode?
required init(_ value: T) {
self.value = value
}
}
class DoublyNode<T>: LinkedNode<T> {
weak var previous: DoublyNode?
override var next: LinkedNode<T>? {
didSet { (next as! Self).previous = self }
}
}
在后面的代码中,每次引用 next
属性 后,我都必须手动将其转换为 DoublyNode(当我使用 DoublyNode 时),因为它始终具有 LinkedNode 类型。
它易于管理,但令人讨厌且丑陋。
Swift 的子 classes 不允许属性被协变覆盖(即,具有作为 super
类型的子类型的类型为此 属性).
这是一个更简单的例子,没有泛型:
class Animal {}
class Bird: Animal {}
class AnimalFeeder {
var animalToFeed: Animal
init() { fatalError("not relevant to the example") }
}
class BirdFeeder: AnimalFeeder {
override var animalToFeed: Bird // error: cannot override mutable property 'animalToFeed' of type 'Animal' with covariant type 'Bird'
}
你有很多示例,除了 Self
从这个类比中同时扮演动物和动物喂食者的角色。
这个数据模型从根本上被打破了
它患有square-rectangle problem。除非 next
和 prev
指针被紧密封装,并且仅通过受控函数公开,否则此 class 会破坏 LSP。
如果您将 DoublyNode<T>
传递给期望 LinkedNode<T>
的算法,该算法可以对链表的结构进行操作,仅将其视为 singly-linked,并忽略prev
参考文献。这样做,它从根本上打破了 doubly-linked 列表的不变性,并使其无效。
didSet
观察器有帮助,但它不会在初始化时触发(因此对象可能从一开始就是错误的)。我认为这一切都可以通过添加更多封装来解决,并使这个对象对其属性更具权威性,这样它们就可以确保它们保持有效。但我认为这是一个有争议的问题,因为还有更深层次的问题:
“这两个 classes 共享 3 个字段中的 2 个”不是继承的良好动机。说真的,你从这份遗产中还能得到什么?我认为拥有两个单独的链接节点 classes 会获得更多价值,每个链接节点由一个 struct SinglyLinkedList<T>
和一个 struct DoublyLinkedList<T>
拥有,它们符合 Collection
和 [=22] =]分别(定义前向和后向迭代)
让我来说明为什么 Swift 不允许这样做。如果它允许你像那样使用 Self
,理论上你可以这样做:
let doublyNode = DoublyNode(1)
let linkedNode: LinkedNode<Int> = doublyNode // this assignment should work, right?
linkedNode.next = LinkedNode(2) // "linkedNode" is of type LinkedNode, so Self is LinkedNode, so I can do this assignment, right?
现在发生了什么? DoublyNode
中的 next
的 didSet
被调用,它尝试访问 LinkedNode(2)
的 previous
。问题是,LinkedNode
甚至没有 previous
属性!因此,让你这样使用Self
是不安全的。
我认为 DoublyNode
根本不应该继承自 LinkedNode
。 Alexander 的回答很好地解释了这一点,即这违反了 LSP。将这两个 类 联系起来可以做的一件事是使用协议:
protocol LinkedNodeProtocol {
associatedtype Data
var value: Data { get set }
var next: Self? { get set }
init(_ value: Data)
}
final class LinkedNode<Data>: LinkedNodeProtocol {
var value: Data
var next: LinkedNode?
init(_ value: Data) {
self.value = value
}
}
final class DoublyNode<Data>: LinkedNodeProtocol {
var value: Data
weak var previous: DoublyNode?
var next: DoublyNode? {
didSet { next?.previous = self }
}
init(_ value: Data) {
self.value = value
}
}