0

对于 Friday Fun,我想以可互换的格式对 Angles 进行建模。我不确定我是否以最 Swift 惯用的方式完成了它,但我正在学习。所以我有一个Angle协议,然后是 3 种不同的结构类型(弧度、度数和旋转),它们都符合角度协议。我希望能够添加/减去它们,但诀窍是,我希望lhs参数决定返回类型。例如:

Degrees(180) + Rotations(0.25) --> Degrees(270)

或者

Rotations(0.25) + Radians(M_PI) -> Rotations(0.75)

我希望我能做类似的事情

func + (lhs:Angle, rhs:Angle) -> Angle {
    return lhs.dynamicType(rawRadians: lhs.rawRadians + rhs.rawRadians)
}

Angle协议需要一个var rawRadians:CGFloat { get }以及一个init(rawRadians:CGFloat)

我可以使用 Smalltalk 式的双重分派方法来做到这一点,但我认为大多数情况下都有更适合 Swift 的方法(尤其是需要较少代码的方法,双重分派需要大量样板代码)。

4

1 回答 1

2

你只需要一个通用的添加:

func +<A: Angle>(lhs: A, rhs: Angle) -> A {
    return A(rawRadians: lhs.rawRadians + rhs.rawRadians)
}

这样,添加将返回 lhs 上的任何类型。

一般来说,如果你正在使用dynamicType,你可能正在与 Swift 作斗争。Swift 更多地依赖泛型和协议(即编译时的静态类型信息)而不是动态调度(即运行时的动态类型信息)。

正如您所说,在此代码中,A是“某种类型,符合Angle,在编译时确定”的占位符。所以在你的第一个例子中:

Degrees(180) + Rotations(0.25) --> Degrees(270)

这实际上调用了一个专门的函数+<Degrees>。和这个:

Rotations(0.25) + Radians(M_PI) -> Rotations(0.75)

调用一个(逻辑上)不同的函数,称为+<Rotations>. 编译器可以选择将这些函数优化为单个函数,但从逻辑上讲,它们是独立的函数,在编译时创建。它基本上是书写addDegrees(Degrees, Angle)addRotations(Rotations, Angle)手写的捷径。

现在,关于一个需要两个角度并返回的函数的问题......好吧,什么?如果你想Angle在这种情况下返回一个,这很容易,并且与你的原始签名完全匹配:

func +(lhs: Angle, rhs: Angle) -> Angle {
    return Radians(rawRadians: lhs.rawRadians + rhs.rawRadians)
}

“但是……”你是说,“那又回来了Radians。” 不,它没有。它返回Angle。你可以在它上面做任何你想做的“角度”。实现细节应该是不透明的。如果您关心底层数据结构是 a Radians,那么您几乎可以肯定做错了什么。

好的,在一种情况下,了解这一点可能很有用,那就是如果您根据您的操作方式打印出来。因此,如果用户在开始时向您提供了学位信息,那么您希望以学位为单位打印所有内容(使用description您未提及的方法)。在那种特殊情况下,也许这值得做。如果你愿意,你的原始代码非常接近:

func +(lhs: Angle, rhs: Angle) -> Angle {
    return lhs.dynamicType.init(rawRadians: lhs.rawRadians + rhs.rawRadians)
}

但重要的是要理解这与您要求“使用 lhs 参数来指定返回类型”的请求不符。这会导致 lhs 参数指示返回实现。返回类型始终为Angle。如果要更改返回类型,则需要使用泛型。

于 2015-09-25T18:23:37.967 回答