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

32

我有两个协议: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
5个回答

23

协议可以相互继承:

协议继承

一个协议可以继承一个或多个其他协议,并且可以在继承的基础上添加更多要求。协议继承的语法类似于类继承的语法,但是可以选择列出多个继承的协议,用逗号分隔:

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


6
是的,但是原帖作者正在使用扩展程序。 - Papershine
1
@paper1111,那又怎样?他展示了他尝试过但不起作用的内容。他特别要求能够使所有的“Pen”也成为“InstrumentForProfessional”。这就是我的答案所做的。 - user28434'mstep
1
我想这是正确的做法。我更喜欢创建一个扩展,这样两个协议就完全独立于彼此。 - Richard Topchii
1
@RichardTopchiy,你不能既拥有协议独立性,又确保所有符合一个协议的类型都符合另一个协议。你不能两全其美。 - user28434'mstep
6
谢谢您的建议,但是 @user28434 您使用的傲慢语气很不受欢迎。让我们友好相待。 - apunn
@apunn,确实,那是2年前的事了,也许那天我心情不好,所以回答得那样。 - user28434'mstep

16

这里是如何在扩展中要求协议符合性的方法。

extension Pen where Self: InstrumentForProfessional {}

你当前的做法会让编译器认为你正在进行继承,而不是遵从协议。

另外请注意,let pen = ApplePen() as InstrumentForProfessional 是没有意义的,也不会编译通过。


谢谢您的回复。在应用您的补丁之后,let pen = ApplePen() as InstrumentForProfessional 是否会编译? - Richard Topchii
1
我的目标是将所有的“Pen”制作成“InstrumentForProfessional”的集合。 - Richard Topchii
1
@paper1111,这不是符合性问题,符合性存在于结构体/类和协议之间,继承存在于协议之间。已经打开官方手册了,上面明确写着“协议继承”。 - user28434'mstep
@RichardTopchiy let pen = ApplePen() as InstrumentForProfessional 仍然无法编译。您无法将类型转换为协议。在使用了 extension 后,Pen 就成为了 InstrumentForProfessional - Papershine
5
@paper1111,不是这样的。您的扩展仅允许指定一些代码,所有符合PenInstrumentForProfessional的类型都将具有该代码,而不是所有符合Pen的类型都符合InstrumentForProfessional。假设协议没有关联类型,将类型的值转换为协议所符合的是有效构造。 - user28434'mstep

4

我认为如果你查看一下这个stackoverflow的回答,它可以解决相同的问题。

https://dev59.com/5VoU5IYBdhLWcg3wk3h2#37353146

@paper1111的答案接近于你所寻找的,但我认为你真正想要做的是:

extension InstrumentForProfessional where Self: Pen {}

由于 Pen 已经符合 InstrumentForProfessional 的要求,所以当它是一个笔时,你只需要扩展 InstrumentForProfessional。

有时候我会忘记在 Swift 中如何使用协议继承,但感谢 Stack Overflow 来提醒我。


3

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

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

相比于@user28434的答案,这似乎更偏离您的要求,并且实际上回答了一个不同的问题(即如何为采用两个不同协议的类型添加功能)。因此,我想问一下您是否真正需要的是类继承而不是协议:

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而不是structenum时呢?
如果您不想应用类继承,并且也不想在编写Pen协议时添加继承,并且如果您也不想向类添加多个协议,那么您可以为了整洁性使用一个类型别名。
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的方法,请这样做。


2
所有上述答案都解释了如何操作,但没有解释原因。
在考虑协议时,您必须进行心理转换 - 协议不是结构。当您定义协议一致性时,您只需要给出一组要求符合类型必须打包的内容。接受或放弃该类型将如何实现它们。
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

一支笔不仅符合专业工具的要求,它本身就是专业工具。另一个例子是有一个专业工具协议和一个弦乐器。你知道,弦乐器不符合专业工具;它是一种专业工具。

我认为你正在寻找某些字段的默认实现。考虑这个例子;每个移动物体都应该告诉它的最大速度。汽车是移动物体吗?是的,它是。它应该符合移动对象吗?不!

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标准,但仍然可以为每个给定的字段提供自己的实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接