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

谁说我不能喝 提交于 2021-02-19 04:24:43

问题


I'm trying to write a protocol that reconciles two different structs that describe the same concept, a stop of some kind. Both have a Code, Description, and Latitude & Longitude coordinates, but for one type, the Description could be nil, and for the other type, the coordinates might be nil.

How can I write a single protocol that reconciles these two structs?

Here's my protocol:

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

And the two types of stops:

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
}

In C# (my mother tongue), I would write an explicit interface implementation like so (pseudo-code):

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

However, I'm not aware of any similar functionality in Swift. Given that I'm unable to change the existing property definitions of BusStop and TrainStop, how can I write the Stop protocol so that it wraps around both structs and returns the properties when available?


回答1:


The desired feature from explicit interface implementations is that they are statically dispatched, right? If you use Description on a BusStop, it will be an optional string, but if you use Description on a Stop, it will be a non-optional string.

In Swift, extension members are statically dispatched, so you can make use of this to achieve something similar:

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

This code shows that this works:

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

However, I still don't think this is good Swift code. It is often bad to just directly translate an API from one language to another. If I were you, I would make Longitude, Latitude and Description in Stop to be all optionals.




回答2:


I agree with @Sweeper, it might be that your design is not the best one, if you found the need to bring different data layouts under the same umbrella. Nonetheless, I want to share another possible approach to your problem.

Instead of having all Stop properties into one protocol, you could shove them in another struct, and have the Stop protocol return that struct:

protocol Stop {
    var stopData: StopData { get }
}

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

You can then add the following conformances for your two structs:

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

This means that you'll be circulating StopData instances in the rest of the app instead of the Stop protocol.

P.S. I also changed the property names to be more in line with the Swift naming guidelines.



来源:https://stackoverflow.com/questions/58280827/how-to-implement-a-swift-protocol-across-structs-with-conflicting-property-names

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!