简体   繁体   中英

Make a protocol conform to another protocol

I have two protocols: Pen and InstrumentForProfessional . I'd like to make any Pen to be an InstrumentForProfessional :

protocol Pen {
  var title: String {get}
  var color: UIColor {get}
}

protocol Watch {} // Also Instrument for professional
protocol Tiger {} // Not an instrument

protocol InstrumentForProfessional {
  var title: String {get}
}

class ApplePen: Pen {
  var title: String = "CodePen"
  var color: UIColor = .blue
}

extension Pen: InstrumentForProfessional {} // Unable to make ApplePen an Instument for Professional: Extension of protocol Pen cannot have an inheritance clause

let pen = ApplePen() as InstrumentForProfessional

Protocols can inherit each other :

Protocol Inheritance

A protocol can inherit one or more other protocols and can add further requirements on top of the requirements it inherits. The syntax for protocol inheritance is similar to the syntax for class inheritance, but with the option to list multiple inherited protocols, separated by commas:

 protocol InheritingProtocol: SomeProtocol, AnotherProtocol { // protocol definition goes here }

So, you basically need to do this:

protocol InstrumentForProfessional {
    var title: String {get}
}

protocol Pen: InstrumentForProfessional {
    var title: String {get} // You can even drop this requirement, because it's already required by `InstrumentForProfessional`
    var color: UIColor {get}
}

Now everything that conforms to Pen conforms to InstrumentForProfessional too.

Here is how you require conformance to protocols in extensions.

extension Pen where Self: InstrumentForProfessional {}

The current way that you are doing makes the compiler think that you are doing inheritance, not protocol conformance.

Also note that let pen = ApplePen() as InstrumentForProfessional doesn't make sense and won't compile.

I think if you check over at this SO answer, it solves the same problem.

https://stackoverflow.com/a/37353146/1070718

@paper1111 was close to what you're looking for, but I think you really want to do :

extension InstrumentForProfessional where Self: Pen {}

Since Pen already conforms to InstrumentForProfessional then you just need to extend InstrumentForProfessional when it is a Pen.

Sometimes I forget how protocol inheritance works in Swift but thanks to SO for refreshing my memory.

Two answers have already been provided: @user28434 is providing you with a solution under the assumption that you can add the conformance at the time that you write the Pen protocol and @paper1111 is providing you with the opportunity to make additions to the Pen extension exclusive to where the type also conforms to InstrumentForProfessional . Note: to take advantage of @paper1111's answer you must also add the protocol to your type like so:

class ApplePen: Pen, InstrumentForProfessional {
  var title: String = "CodePen"
  var color: UIColor = .blue
}

Which seems to stray further from your requirements than the answer from @user28434, and in fact is answering a different question (which is how to add functionality to a type that adopts two different protocols). Therefore I would ask whether what you are actually looking for is not protocol but class inheritance:

class InstrumentForProfessional {
    var title: String
    init(title:String) {
        self.title = title
    }
}

class Pen: InstrumentForProfessional {
    var color: UIColor
    init(title:String, color:UIColor) {
        self.color = color
        super.init(title: title)
    }
}

Because it seems that what you are getting at through the existence of the title property in both is the overriding behaviour common to class inheritance. So the question becomes why wrestle to squeeze class inheritance into a protocol when you are using class rather than struct or enum anyway?

If you don't want to apply class inheritance and you don't want to add inheritance at the time of writing the Pen protocol, and if you also don't want to add multiple protocols to your class, then one other thing you could do for neatness is to use a typealias:

protocol InstrumentForProfessional {
    var title: String {get}
}

protocol PenExtra {
    var color: UIColor {get}
    var title: String {get}
}

typealias Pen = InstrumentForProfessional & PenExtra

class ApplePen: Pen {
    var title = "CodePen"
    var color = UIColor.blue
}

But having written all this, if you can follow @user28434's approach then do so.

All the above answers explain how to do it, but not why .

When thinking about protocols, you have to make a mental switch - protocols are not structures. When you define protocol conformance, you only give a set of required things that a conforming type has to pack. Give or take how that type is going to implement them.

protocol InstrumentForProfessional {
  var title: String {get}
}

protocol Pen: InstrumentForProfessional {
  var title: String {get}
  var color: UIColor {get}
}

protocol Watch: InstrumentForProffesional {}
protocol Tiger {} // Not an instrument

A Pen doesn't conform to InstrumentForProfessional. It is InstrumentForProfessional. Another example would be having Instrument protocol and having a StringInstrument. You know, StringInstrument doesn't conform to Instrument; it is an Instrument.

What I think you are looking for is default implementations of certain fields. Consider this example; every MovingObject should tell its maximum speed. Is Car a moving object? Yes, it is. Should it conform to MovingObject? NO!

protocol MovingObject {
    /// Top speed a vehicle can reach in km/h.
    var topSpeedKMH: Double { get }
}

protocol Car: MovingObject {
    /// Horsepower of a car.
    var hp: Double { get }
    var weight: Double { get }
    
    // Note that topSpeed is also required,
    // but since we specified it in MovingObject we don't have
    // to rewrite it here.
    // var topSpeedKMH: Double { get }
}

But we know that we can calculate top speed from horsepower and weight. That's why we create a default implementation.

extension Car {
    var topSpeedKMH: Double {
        hp * weight
    }
}

Every Car now can conform to a MovingVehicle "out-of-the-box"; yet, it could still provide its own implementation for every given field.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM