Tap
是一个微型库 (仅 11 行代码!),让您可以在实例初始化后对其进行配置,而不会牺牲代码的语义。它的工作方式类似于 Ruby 的 #tap
。
Swift 的 struct
的人体工程学设计非常出色。想象一下这个结构体
struct Person {
let name: String
let age: Int
}
您无需额外的工作即可获得一个初始化器 Person(name:age:)
,如果您将 let
变为 vars
并提供默认值,您甚至可以获得更多初始化器。想象一下这个 Person
结构体:
struct Person {
var name: String = ""
var age: Int = 0
}
然后,除了 Person(name:age:)
之外,您还将获得 Person()
、Person(name:)
和 Person(age:)
,而无需编写任何额外的代码。当您想要一些临时的实例时,这种自动合成功能非常出色。
如果您想向结构体添加另一个带有默认值的字段,比如 phoneNumber
,
struct Person {
var name: String = ""
var age: Int = 0
var phoneNumber: String = ""
}
那么程序的其余部分(还记得我们以前称应用为“程序”吗?)将继续工作而无需修改。好的代码是可塑的——易于适应新的需求。
可悲的是,当您想将一个 struct
从一个模块引入到另一个模块时,所有这些都会失效。(这种情况经常发生在我身上,因为我是 The Composable Architecture 的粉丝,并且我喜欢将我的应用程序拆分为独立的基于功能的包。)
在这种情况下,您需要在您想要使用的 struct
中声明一个 public
初始化器。
struct Person {
init(name: String = "", age: Int = 0) {
self.name = name
self.age = age
}
var name: String
var age: Int
}
代码现在增加了一倍的长度!更糟糕的是,假设我们添加一个电话号码
public struct Person {
public init(name: String = "", age: Int = 0, phoneNumber: String = "") {
self.name = name
self.age = age
self.phoneNumber = phoneNumber
}
public var name: String
public var age: Int
public var phoneNumber: String
}
为了不破坏 Person
的现有客户端,我们不得不修改三行不同的代码,而我们上面只做了一行更改。
如果您使用无参数的 init
并在之后配置实例,人体工程学设计会大大改善。
public struct Person {
public init() {}
public var name: String = ""
public var age: Int = 0
}
var john = Person()
john.name = "John"
john.age = 41
现在,添加新字段将产生更可控的连锁反应。
但是,我认为现在的代码更弱了。我们将初始化与配置分离,因此它不如以前那样清晰地揭示意图。此外,它在语义上可能是错误的:现在 john
强制成为 var
,而不管我们之后是否要改变它。
Tap
通过为您提供一个协议 Tappable
来解决这个问题,它允许您这样做
public struct Person: Tappable {
public init() {}
public var name: String = ""
public var age: Int = 0
}
let john = Person().tap { john in
john.name = "John"
john.age = 41
}
现在,您的结构体可以轻松地跨模块更改,并且您的代码就像您使用合成的初始化器一样清晰。
请注意,Tap
还以另一种方式改进了人体工程学设计:初始化器需要特定的参数顺序,而配置块则没有这种约束。
如果您的结构体符合 DefaultConstructible
(由我们提供),您甚至可以使用 .tap
作为静态函数,从而更加简洁。我发现这在 The Composable Architecture 中派生结构体时特别有用
public struct PersonState: Equatable, Tappable, DefaultConstructible {
public init() {}
public var name: String = ""
public var age: Int = 0
}
struct AppState: Equatable {
public var name: String = ""
public var age: Int = 0
var personState: PersonState {
.tap { state in
state.name = name
state.age = age
}
}
}