在我正在做的一个项目中,有大量关于度量单位的处理。(或许我应该继续去推进项目进展而不是在这儿写这篇博客)
我总是觉得对度量单位的建模是一件很有意思的事情。比如说,时间单位,假设你有一个 API 接受一个时间作为参数,可能需要以秒作为单位(又或者是毫秒作为单位)但是,有的时候你有需要以小时为单位来表达时间,例如 2 小时。所以,为了避免魔数(magic number) (7200
两小时转换为秒为单位),你会使用 2 * 60 * 60
,为了增加可读性还会在其中的操作符之间加上空格。
然而,7200 这个数字并没有什么实际意义。如果你对着这个数字看很长时间,拥有很好的数学计算功底的话,有可能你会猜到这个数字代表着两个小时包含的秒数。然而如果不是整数个小时换算成秒的话,你可能永远也猜不出来。
在你的应用中,7200 这个魔数不断的传递和使用,这个数字的度量单位是什么将变得越来越不清晰。
我们需要使用一种方式,将整数和元数据的信息关联起来。将类型描述为度量单位 这篇文章有过相关的讨论,但是我们能否解决不同单位的度量问题,并且使用类型来描述呢?如果这样的话,当我们用 2 小时加上 30 分钟的时候,就不会得到 32 这样无意义的结果。
(当然,也有一些语言层面解决这个问题的方案,但是大多数的语言不支持这样的操作)
我们仍然希望能够用 2 个小时加上 30 分钟,并且得到一个有意义的结果,所以在我们的类型系统中,Time
需要是一个度量实体,同时 Hours
和 Seconds
也需要是。
表示 Time
的可以有多个单位,这些单位都必须能够以秒的方式来度量。
swif protocol Time { var inSeconds: Double { get } }
每个时间的单位类型都是独立的,但是都需要遵守 Time 协议。
struct Hours: Time { let value: Double var inSeconds: Double { return value * 3600 } } struct Minutes: Time { let value: Double var inSeconds: Double { return value * 60 } }
我们可以使用同样的方式来定义 Seconds
,Days
,Weeks
等等结构体,需要注意的是,如果时间单位过大,将会损失一些精度。
现在我们理解了度量单位的刻画方式,我们可以来进行一些度量单位的计算了。
func + (lhs: Time,rhs: Time) -> Time { return Seconds(value: lhs.inSeconds + rhs.inSeconds) }
我们还可以加上一些好用的扩展:
extension Time { var inMinutes: Double { return inSeconds / 60 } var inHours: Double { return inMinutes / 60 } }
为系统的 Int
类型加上类似 DSL 的扩展,代码思路是从 ActiveSupport 中借鉴的:
extension Int { var hours: Time { return Hours(value: Double(self)) } var minutes: Time { return Minutes(value: Double(self)) } }
利用我们的类型系统,我们可以写出短小但是表意明确的代码:
let total = 2.hours + 30.minutes
(当然结果是以 Seconds
为单位表示的,可能我们希望引入一个表示层来将这个结果以更加有意义的方式展现给用户。我在做的那个项目中有这方面的工作,不过是用 JavaScript 写的,不支持这样的类型系统,就少了很多乐趣)
本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg。