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。除非 nextprev 指针被紧密封装,并且仅通过受控函数公开,否则此 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 中的 nextdidSet 被调用,它尝试访问 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
    }
}