【问题标题】:Extending a type to have multiple values for the same associatedtype将类型扩展为具有相同关联类型的多个值
【发布时间】:2021-01-29 16:52:41
【问题描述】:

想出一个准确的标题有点困难,所以我将用一个简单的例子来说明我的问题。我想创建一个协议,指定一个坚持者能够被转换成不同的类型:

protocol Transformable {
    associatedtype TransformableTo
    func transform() -> TransformableTo
}

然后为每种类型创建特定的协议:

protocol DoubleTransformable: Transformable where TransformableTo == Double {}

例如,如果我希望字符串能够转换为双精度,我会写:

extension String: DoubleTransformable {
    func transform() -> Double {
        return Double(self)
    }
}

这很有效,直到我想让给定类型符合这些“特定”可转换合同中的更多:

protocol DecimalTransformable: Transformable where TransformableTo == Decimal {}
extension String: DecimalTransformable {
    func transform() -> Decimal {
        ...
    }
}

由于DecimalTransformableTransformableTo 类型与DoubleTransformableTransformableTo 类型冲突,编译器在上面的String 扩展上对我咆哮。这对我来说很有意义,但我没有看到解决方法。任何关于如何解决这个问题和/或完成相同事情的不同方向的建议将不胜感激。

编辑

添加更多关于用例的上下文以帮助解决 cmets 中的一些问题。这是实际代码的简化版本,所以这可能看起来有点奇怪,但我认为它明白了这一点。

我有两个结构,一个代表面向公众的模型,一个代表“支持”模型的数据。我不想将 PrivateModel 中的数据暴露给客户端,因此 PublicModel 充当 PrivateModel 和客户端之间的中间人。我要解决的问题是,有时 PrivateModel 属性的类型与我希望 PublicModel 的相应属性的类型不同(并且更改 PrivateModel 的类型是不可能的)。

struct PrivateModel {
    var date: String { return "Jan. 1, 1970" }
}
struct PublicModel {
    private let privateModel: PrivateModel

    init(source: PrivateModel) {
        self.privateModel = source
    }

    var date: Date {
        return self.privateModel.date.transform()
    }
}

注意公共模型中对transform 的调用。我的实际目标是拥有一个可以在任何类型对象上调用的单一转换方法,其中 transform 的通用返回类型由我所在的 getter 的属性类型推断。

【问题讨论】:

  • associatedtype = TransformableTo 没有任何意义。你的意思是associatedtype TransformableTo
  • 正确 -- 已编辑
  • 我认为这里的问题与多态性(方法重载)有关,仅在方法参数更改但返回值不更改时才有效,并且如果协议包含一个方法,则符合类型只能实现此方法的 一个 版本。我正在玩弄有两个关联类型“from”和“to”,并且还通过更改方法的签名而不是只有关联类型“to”但作为方法的参数,但我没有得到任何一个工作。
  • 更进一步的 Joakim 的观点 - Transformable 协议指定一个符合的类型将为 TransformableTo 定义一个 concrete 类型 - DoubleDecimal 或任何其他类型 - 这就是 transform 将返回的内容。所以,不可能是不同的。遇到泛型和/或协议问题的最常见方法是,您首先尝试定义协议,而不是从特定案例开始,然后添加另一个案例,然后查看是否有协议甚至是有道理的。那么……你打算如何使用这种类型?那么,什么是需要协议的用例呢?
  • 恢复关联类型不是通用的。我同意 New Dev 关于需要使用协议的观点。这可以通过扩展 String 并创建一些通用方法来轻松解决

标签: ios swift protocols


【解决方案1】:

不是对您问题的直接回答,但正如其他人已经提到的,关联类型不是通用的。你需要的是一个通用的方法。要完成您想要的并涵盖大多数数字类型,您还需要使泛型类型符合 LosslessStringConvertible 协议。请注意,协议的使用不是必需的,可以删除。我会这样做:

protocol NumericTransformable {
    func numeric<N: Numeric & LosslessStringConvertible>() -> N?
}

extension String: NumericTransformable {
    func numeric<N: Numeric & LosslessStringConvertible>() -> N?  { N(self) }
}

用法:

let double: Double? = "123.45".numeric()       // 123.45
let float: Float? = "123.45".numeric()         // 123.45
let float80: Float80? = "123.45".numeric()     // 123.45
let int: Int? = "123".numeric()                // 123

另请注意,Decimal 和 CGFloat 类型不符合 LosslessStringConvertible

extension CGFloat: LosslessStringConvertible {
    private static let formatter = NumberFormatter()
    public init?(_ description: String) {
        guard let cgFloat = Self.formatter.number(from: description) as? CGFloat else { return nil }
        self = cgFloat
    }
}

extension Decimal: LosslessStringConvertible {
    public init?(_ description: String) {
        guard let decimal = Decimal(string: description) else { return nil }
        self = decimal
    }
}

let cgfloat: CGFloat? = "123.45".numeric()     //  123.45
let decimal: Decimal? = "123.45".numeric()     //  123.45

【讨论】:

  • 非常感谢这些信息。这可能对我的具体情况没有帮助,因为我需要的东西不仅适用于数字,还适用于其他类型(包括自定义类型),但仍然是一种聪明的方法。谢谢!
  • 另外,我认为let int 行有错字。 numeric() 在那种情况下不会返回 123.45,对吧?
猜你喜欢
  • 1970-01-01
  • 2018-05-13
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-14
  • 1970-01-01
相关资源
最近更新 更多