有没有办法基于参数类型简化这种"重载矩阵",这些参数类型最终都可以由特定类型表示?



我们试图创建一个函数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.buyPurchase.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
}

否,不能通过扩展使一个协议与另一个协议一致。该语言不支持它。

相关内容

  • 没有找到相关文章

最新更新