使一个协议符合另一个协议

Make a protocol conform to another protocol

我有两个协议:PenInstrumentForProfessional。我想让任何 Pen 成为 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

以下是您要求在扩展中遵守协议的方式。

extension Pen where Self: InstrumentForProfessional {}

你目前的做法让编译器认为你在​​做继承,而不是协议一致性。

另请注意,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
}

所以,你基本上需要这样做:

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

现在所有符合 Pen 的东西也都符合 InstrumentForProfessional

已经提供了两个答案:@user28434 为您提供了一个解决方案,假设您可以在编写 Pen 协议时添加一致性,@paper1111 为您提供了在类型也符合 InstrumentForProfessional 的情况下,有机会对 Pen 扩展进行独占添加。注意:要利用@paper1111 的答案,您还必须像这样将协议添加到您的类型中:

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

这似乎比@user28434 的回答更偏离您的要求,实际上是在回答一个不同的问题(即如何向采用两种不同协议的类型添加功能)。因此我想问一下你真正要找的是不是协议而是 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)
    }
}

因为看起来你通过 title 属性 的存在得到的是 class 继承共有的压倒一切的行为。所以问题变成了为什么当你使用 class 而不是 structenum 时,为什么要努力将 class 继承压缩到协议中?

如果你不想应用class继承并且你不想在编写Pen协议时添加继承,并且你也不想将多个协议添加到您的 class,然后您可以为整洁做的另一件事是使用类型别名:

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
}

但是写了所有这些,如果你能遵循@user28434 的方法,那就这样做吧。

我认为如果您查看此 SO 答案,它可以解决同样的问题。

@paper1111 接近您要找的东西,但我认为您真的想做:

extension InstrumentForProfessional where Self: Pen {}

既然 Pen 已经符合 InstrumentForProfessional 那么你只需要在它是 Pen 时扩展 InstrumentForProfessional。

有时我会忘记协议继承在 Swift 中是如何工作的,但感谢 SO 让我恢复了记忆。

以上所有答案都解释了如何去做,但没有为什么

在考虑协议时,您必须进行心理转换 - 协议不是结构。当你定义协议一致性时,你只给出了一组必需的东西,一个一致性类型必须打包。给出或接受该类型将如何实现它们。

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

Pen 不符合 InstrumentForProfessional。它 InstrumentForProfessional。另一个例子是拥有 Instrument 协议和 StringInstrument。你知道,StringInstrument 不符合 Instrument;它一种乐器。

我认为您正在寻找的是某些字段的默认实现。考虑这个例子;每个 MovingObject 都应告知其最大速度。汽车是运动物体吗是的。它应该符合 MovingObject 吗? 没有!

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

但我们知道我们可以根据马力和重量计算出最高速度。这就是我们创建默认实现的原因。

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

现在每辆车都可以符合“开箱即用”的 MovingVehicle;然而,它仍然可以为每个给定字段提供自己的实现。