Protocol Inheritance - overriding method parameters

516 views Asked by At

I am trying to convert a design of mine to be pop. However I am stuck and already have bunch of threads for my approaches -not a duplicate of this question though- and apparently they are all dead end.

My question is, is there a way to override parameter types of a protocol method which inherited from another protocol?

struct Books: Codable {}

protocol Listener {
    func listen(_ param: Codable)
}

protocol BooksListener: Listener {
    func listen(_ param: Books)
}


class MyClass: BooksListener {
    // I want only this one to required with the type.
    func listen(_ param: Books) {
        <#code#>
    }
    
    func listen(_ param: Codable) {
        <#code#>
    }
}

I did my research and I believe this is not how protocols work. I am just seeking a solution to this.

I tried to add a associatedType to the Listener and use it as the type inside listen(_:). But this solution restricts any class to have conformance to multiple protocols which inherits from Listener. Details can be found here

1

There are 1 answers

1
Rob Napier On

As a general rule, Swift protocols are not covariant. But your question is a special case, where, even if Swift allowed protocol covariance, your approach couldn't work.

In your example, a BooksListener is required to do everything a Listener can do. A Listener can take an arbitrary Codable as its parameter to listen. That means that a BooksListener must also be able to take an arbitrary Codable. Consider the following:

struct Books: Codable {}

protocol Listener {
    func listen(_ param: Codable)
}

protocol BooksListener: Listener {
    func listen(_ param: Books)
}

class MyClass: BooksListener {
    // Illegal, but assuming you had what you're asking for
    func listen(_ param: Books) {}
}

let l: Listener = MyClass()
l.listen("Strings are Codable, and Listener accepts Codable")

What would this do? This is violating LSP.

What you might want here is an associatedtype as you mention (but I'll explain later why you probably don't). With an associatedtype, you can define Listener the way I expect you mean.

struct Books: Codable {}

protocol Listener {
    associatedtype Parameter: Codable
    func listen(_ param: Parameter)
}

protocol BooksListener: Listener where Parameter == Books {
    func listen(_ param: Books)
}

class MyClass: BooksListener {
    func listen(_ param: Books) {}
}

Why don't you want this? Because it almost certainly doesn't mean anything. What can I possibly do with an arbitrary Listener? I can't call listen on it; I don't know the type of Parameter. There's no algorithm that it helps. And enabling algorithms is what associatedtypes are about.

But the real point is that "POP" doesn't mean "use lots of protocols" and absolutely doesn't mean "recreate class inheritance with protocols." It means "start with concrete types and extract protocols to share algorithms." So before you create a single protocol, you need to ask, what other kinds of Listeners exist in your program, and what algorithm needs to work on an arbitrary Listener? If there's no such algorithm, there should be no protocol.