问题
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