我们试图创建一个函数addQueryItem
,它最终在内部使用一个字符串和一个可选字符串。
为了在API中获得更大的灵活性,我们不使用参数类型的String
,而是使用CustomStringConvertible
(String实现的(,这样我们就可以使用任何可以表示为字符串的内容。
此外,为了给它传递基于String
的枚举,我们还希望它接受RawRepresentable
类型,其中RawValue
本身就是CustomStringConvertible
。
然而,由于我们现在在技术上为每个参数接受两种不同类型的值,我们最终不得不为这两种类型的每个组合创建一个"重载矩阵"——总共四个。
我的第一个想法是通过扩展RawRepresentable
来使用面向协议的编程,这样它就可以在RawValue
也是CustomStringConvertible
的情况下遵守CustomStringConvertible
。然后,我可以直接将其传递给采用两个CustomStringConvertible
参数并消除其他三个参数的版本。然而,编译器不喜欢它,因为我试图扩展一个协议,而不是一个具体的类型。
// This doesn't work
extension RawRepresentable : CustomStringConvertible
where RawValue:CustomStringConvertible {
var description: String {
return self.rawValue
}
}
由于无法做到以上所述,我不得不拥有以下四项:
func addQueryItem(name:CustomStringConvertible, value:CustomStringConvertible?){
if let valueAsString = value.flatMap({ String(describing:$0) }) {
queryItems.append(name: String(describing:name), value: valueAsString)
}
}
func addQueryItem<TName:RawRepresentable>(name:TName, value:CustomStringConvertible?)
where TName.RawValue:CustomStringConvertible {
addQueryItem(name: name.rawValue, value: value)
}
func addQueryItem<TValue:RawRepresentable>(name:CustomStringConvertible, value:TValue?)
where TValue.RawValue:CustomStringConvertible {
addQueryItem(name: name, value: value?.rawValue)
}
func addQueryItem<TName:RawRepresentable, TValue:RawRepresentable>(name:TName, value:TValue?)
where TName.RawValue:CustomStringConvertible,
TValue.RawValue:CustomStringConvertible
{
addQueryItem(name: name.rawValue, value: value?.rawValue)
}
那么,既然看起来不可能使RawRepresentable
遵守CustomStringConvertible
,那么有没有其他方法来解决这个"过载矩阵"问题?
为了扩展我的评论,我相信你在与Swift类型的系统作斗争。在Swift中,您通常不应该尝试自动转换类型。调用方在需要某个功能时应明确地符合其类型。因此,对于您的Order
枚举示例,我认为它应该以这种方式实现:
首先,有一个名称和值的协议:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
现在,对于字符串可转换枚举,最好不必自己实现。
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
但是,为了类型安全,您需要明确地遵守协议。这样,你就不会与那些本不应该以这种方式使用的东西发生冲突。
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
也许QueryItems
真的需要字符串。好的。
class QueryItems {
func append(name: String, value: String) {}
}
但是包装这个的东西可以是类型安全的。这样Order.buy
和Purchase.buy
就不会碰撞(因为它们不能同时通过(:
class QueryBuilder<Name: QueryName, Value: QueryValue> {
var queryItems = QueryItems()
func addQueryItem(name: QueryName, value: QueryValue?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
您可以使用上面的内容来降低类型安全性(使用StringCustomConvertible
和使QueryBuilder
成为非泛型,我不建议这样做,但您可以这样做(。但我仍然强烈建议您让调用者通过显式标记(而不是其他(它们符合协议来显式标记他们计划以这种方式使用的类型。
显示不太安全的版本:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
extension QueryName where Self: CustomStringConvertible {
var queryName: String { return self.description }
}
extension QueryValue where Self: CustomStringConvertible {
var queryValue: String { return self.description }
}
class QueryItems {
func append(name: String, value: String) {}
}
class QueryBuilder {
var queryItems = QueryItems()
func addQueryItem<Name: QueryName, Value: QueryValue>(name: Name, value: Value?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
否,不能通过扩展使一个协议与另一个协议一致。该语言不支持它。