Money 是一个便于处理和显示货币及金额的包。
Decimal 类型来表示金额来实现的。 Decimal 不会遭受(可能)不精确的表示形式的影响,这使得浮点数不适合作为金额的载体。Tender)泛型化其货币来实现的,从而在处理货币类型时启用编译器级别的支持。Money 协议实现的。货币由符合 Currency 协议的无大小写枚举表示。我们使用无大小写枚举,因为我们只想将货币用作类型。 我们不需要货币的实例,因为我们不需要它们,并且无大小写枚举无法实例化。货币具有 code 和 minorUnitScale。 minorUnitScale 表示主(主要)货币单位的细分。例如:欧元和美元分为美分,因此它们的 minorUnitScale 为 2。每种货币也有一个名称。我们从平台派生(本地化)名称,因为 Apple 平台提供此信息。
我们为协议中定义的所有属性提供默认实现,以避免代码重复。唯一预计偶尔会被覆盖的实现是 minorUnitScale 的实现。绝大多数货币的单位刻度为 2,但有些货币偏离了这一点。我们提供了一个已知货币的默认列表,我们预计很少需要更新该列表。我们提供的列表基于 Apple 平台已知的常见货币,因此不能保证与 ISO 4217 货币列表完全 100% 相同,但在撰写本文时,差异非常小,可以忽略不计。但是,您的需求可能会有所不同,因此您可以选择根据需要更改列表。
对于金额的表示,我们使用两种相关类型:Money,一个表示金额的协议;以及 Tender,一种符合 Money 的类型。 Tender 是泛型化的,它代表的货币,这提供了编译器辅助的类型安全(例如,禁止添加不同货币的 Tender),而它对 Money 协议的符合性提供了必要的灵活性,可以处理各种货币的集合,以及在编译时货币未知的情况下运行时实例化金额。
将 Money 视为抽象类型,在运行时提供动态灵活性,并将 Tender 视为用于处理的具体金额。
虽然您不能添加或减去不同货币的 Tender(如果不首先将它们转换为相同的货币,这将毫无意义),或者将一个 Tender 乘以另一个 Tender,但您可以对 Tender 执行算术运算。例如,您可以将 Tender 乘以一个数字类型:€5.42 * 3.14。
货币已经提供,因此您无需创建货币。但是,如果出于任何原因,您发现需要创建自定义货币,则可以将新的货币类型创建为符合 Currency 协议的枚举。如果您的自定义货币需要的 minorUnitScale 不是标准的 2,那么您需要在枚举的主体中提供自定义实现
public enum MyCustomCurrency: Currency { public static var minorUnitScale: Int { 4 } }
否则,您可以创建一个带有空主体的枚举
public enum MyCustomCurrency: Currency {}
通过创建 Tender 的实例来创建具体的金额,并提供它泛型化的货币。最好从 Decimal、Int 或 String 创建 Tender,而不是从浮点值创建,尽管如果您真的需要,也可以使用它们。
let amount: Decimal = 3.14 // (or, preferably: Decimal(string: "3.14"))
let someMoney1 = Tender<EUR>(amount)
let someMoney2 = Tender<USD> = 5
let someMoney3 = Tender<JPY> = 3000
let someMoney4 = Tender<SEK>("42")
let someMoney5 = Tender<USD>("5,501", locale: Locale(identifier: "nl_NL")
let someMoney6 = Tender<USD>("5.501", locale: Locale(identifier: "en_US")
let ratherNot = Tender<EUR>(3.02) // 3.02 cannot be represented accurately as a floating point number, hence the tender initiated from it will inherently be flawed.
您不能添加或减去不同货币的 Tender。但是,您可以添加和减去相同货币的 Tender
let sum = someMoney5 + someMoney6
您还可以比较相同货币的 Tender
if someMoney5 < someMoney6 {
…do something…
}
查看 TenderTests 和 TenderAlgebraTests 文件,以获得对可能内容的良好概述。
当然,我们通常需要向用户显示我们的金额。为此,Money 类型提供了一个类型为 Displayable 的 displayable,它有助于将金额转换为用户友好的字符串。当您想向用户显示金额时,向实例请求 displayable,并向 Displayable 请求所需的字符串
someMoney5.displayable.formatted
someMoney5.displayable.formattedTruncatingDecimals()
someMoney5.displayable.formattedTruncatingDecimals(for: Locale(identifier: "en_GB")
someMoney5.displayable.formattedWithoutCurrencySymbol()
someMoney5.displayable.formattedWithoutCurrencySymbol(for: Locale(identifier: "pt_PT")
您还可以向 displayable 请求货币符号、小数点分隔符和货币名称。
如果您需要存储具有各种货币的金额的集合,则无法创建 Tender 的集合,因为 Tender 是泛型化的,它代表的货币。 在这种情况下,您应该创建一个 Money 的集合,因为 Money 虽然持有货币,但不是泛型化的。
var variousCurrencies = [any Money]()
variousCurrencies.append(Tender<EUR>(1))
variousCurrencies.append(Tender<USD>(0.5))
此外,如果您需要在运行时创建金额,而您在编译时不知道其货币,请使用 Money 类型来建模您的数据。 使用 MoneyFactory 从金额和货币代码获取具体的 Tender(类型为 Money)
let myRuntTimeAmount = Decimal(42)
let myRuntimeCurrency = SupportedCurrency.INR
try MoneyFactory.moneyFrom(amount: myRuntTimeAmount, currency: myRuntimeCurrency)