首页 > 解决方案 > 有没有办法根据最终都可以由特定类型表示的参数类型来简化这个“重载矩阵”?

问题描述

我们正在尝试创建一个addQueryItem最终在内部使用字符串和可选字符串的函数。

为了在 API 中获得更大的灵活性,而不是String用于参数类型,我们改为使用CustomStringConvertible(String 实现的),因此我们可以使用任何可以表示为字符串的东西。

此外,因此我们可以传递String基于 it 的枚举,我们还希望它接受自身为 a 的RawRepresentable类型。RawValueCustomStringConvertible

然而,由于我们现在在技术上接受每个参数的两种不同类型的值,我们最终不得不为这两种类型的每种组合创建一个“重载矩阵”——总共四个。

我的第一个想法是通过扩展来使用面向协议的编程,RawRepresentable因此CustomStringConvertible如果它RawValue也是一个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,还有其他方法可以解决这个“重载矩阵”问题吗?

标签: swiftprotocolsextension-methodsrawrepresentablecustomstringconvertible

解决方案


为了扩展我的评论,我相信您正在与 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
}

推荐阅读