如何跨具有冲突 属性 名称的结构实现 Swift 协议

How to implement a Swift protocol across structs with conflicting property names

我正在尝试编写一个协议来协调描述相同概念的两个不同结构,某种停止。两者都有 CodeDescriptionLatitude & Longitude 坐标,但对于一种类型,Description 可能是 nil,对于另一种类型,坐标可能是 nil

如何编写一个协议来协调这两个结构?

这是我的协议:

protocol Stop {
    var Code : String { get }
    var Description : String { get }
    var Latitude : Double { get }
    var Longitude : Double { get }
}

以及两种类型的止损:

struct BusStop : Stop {  // Compiler error: doesn't implement Description
    var Code : String
    var Description : String?
    var Latitude : Double
    var Longitude : Double
    // Various other properties
}

struct TrainStop : Stop {  // Compiler error: doesn't implement Latitude or Longitude
    var Code : String
    var Description : String
    var Latitude : Double?
    var Longitude : Double?
    // Various other properties
}

在 C#(我的母语)中,我会像这样编写一个显式接口实现(伪代码):

// At the end of the BusStop struct
var Stop.Description : String { return Description ?? string.Empty }

// At the end of the TrainStop struct
var Stop.Latitude : Double { return Latitude ?? 0 }
var Stop.Longitude : Double { return Longitude ?? 0 }

但是,我不知道 Swift 中有任何类似的功能。鉴于我无法更改 BusStopTrainStop 的现有 属性 定义,我该如何编写 Stop 协议,以便它环绕结构和 returns 属性何时可用?

显式接口实现的理想特性是它们是静态分派的,对吧?如果你在 BusStop 上使用 Description,它将是一个可选字符串,但如果你在 Stop 上使用 Description,它将是一个非可选字符串。

在Swift中,扩展成员是静态调度的,所以你可以利用它来实现类似的东西:

extension Stop where Self == BusStop {
    // Since the type of "self" here is BusStop, "Description" refers to the one declared in BusStop
    // not this one here, so this won't cause infinite recursion
    var Description : String { return self.Description ?? "" }
}

extension Stop where Self == TrainStop {
    var Latitude: Double { return self.Latitude ?? 0 }
    var Longitude: Double { return self.Longitude ?? 0 }
}

这段代码表明这是可行的:

let busStop = BusStop(Code: "ABC", Description: "My Bus Stop", Latitude: 0, Longitude: 0)
print(type(of: busStop.Description)) // Optional<String>
let stop: Stop = busStop
print(type(of: stop.Description)) // String

但是,我仍然不认为这是好的 Swift 代码。直接将 API 从一种语言翻译成另一种语言通常是不好的。如果我是你,我会把 LongitudeLatitudeDescription in Stop 全部设为可选项。

我同意@Sweeper 的观点,如果您发现需要将不同的数据布局置于同一保护伞下,那么您的设计可能不是最好的。尽管如此,我想分享另一种可能的方法来解决您的问题。

不是将所有 Stop 属性都放入一个协议中,而是可以将它们推入另一个结构中,并让 Stop 协议 return 结构为:

protocol Stop {
    var stopData: StopData { get }
}

struct StopData {
    var code: String
    var stopDescription: String
    var latitude: Double
    var longitude: Double
}

然后您可以为您的两个结构添加以下一致性:

struct BusStop: Stop {
    var code: String
    var busStopDescription: String?
    var latitude: Double
    var longitude: Double

    var stopData: StopData {
        return StopData(code: code,
                        stopDescription: busStopDescription ?? "",
                        latitude: latitude,
                        longitude: longitude)
    }
}

struct TrainStop: Stop {
    var code: String
    var trainStopDescription: String
    var latitude: Double?
    var longitude: Double?

    var stopData: StopData {
        return StopData(code: code,
                        stopDescription: trainStopDescription,
                        latitude: latitude ?? 0,
                        longitude: longitude ?? 0)
    }
}

这意味着您将在应用程序的其余部分而不是 Stop 协议中循环 StopData 个实例。

P.S。我还更改了 属性 名称,使其更符合 Swift 命名准则。