Stored variable of Self type (especially when subclassing)

喜欢而已 提交于 2021-01-02 12:53:53

问题


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 }
    }
}

I wish it compiles like this. But it doesn't.

Stored property cannot have covariant 'Self' type

I have to rewrite the code to make it compile and work:

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 }
    }
}

And everywhere in the later code every time after referring to next property I have to manually cast it to DoublyNode (when I'm working with DoublyNode) because it always has type LinkedNode.
It's manageable but annoying and ugly.


回答1:


Let me demonstrate why Swift disallows this. If it did allow you to use Self like that, you could in theory do:

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?

Now what happens? The didSet of next in DoublyNode gets called, and it tries to access previous of LinkedNode(2). The thing is, LinkedNode doesn't even have a previous property! Therefore, allowing you to use Self this way is unsafe.

I don't think DoublyNode should inherit from LinkedNode at all. Alexander's answer explains this very well, namely that this violates the LSP. One thing you could do to relate these two classes, is with a protocol:

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
    }
}



回答2:


Swift's subclasses don't allow properties to be covariently override (that is, to have a type that is a subtype of the super's type for that property).

Here's a simpler example, without the generics:

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'
}

You have pretty much the sample example, except Self is playing both the animal and the animal feeder roles from this analogy.

This data model is fundamentally broken

It suffers from the square-rectangle problem. Unless the next and prev pointers are tightly encapsulated, and exposed only through controlled functions, then this class breaks the LSP.

If you were to pass a DoublyNode<T> to an algorithm that expects a LinkedNode<T> that algorithm can operate upon the structure of the linked list, only treating as singly-linked, and ignoring the prev references. In doing so, it's fundamentally broken the invariant of the doubly-linked list, and made it invalid.

The didSet observer helps, but it doesn't fire upon initialization (so the object can be wrong from the start). I think this is all fixable by adding more encapsulation, and making this objects more authoritative over their properties, so that they can ensure that they stay valid. But I think that's a moot point, because there's a deeper issue:

"These two classes share 2 out of 3 fields" isn't a good motivation for inheritance. And really, what else do you gain from this inheritance? I think you would get much more value from having two seperate linked node classes, each owned by a struct SinglyLinkedList<T> and a struct DoublyLinkedList<T>, which conform to Collection and BidirectionalCollection respectively (which defines forward and backward iteration)



来源:https://stackoverflow.com/questions/63964975/stored-variable-of-self-type-especially-when-subclassing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!