本文介绍了Go中的功能选项以及如何使用Options模式实现它们。
功能选项采用函数额外参数的形式,扩展或修改其行为。以下是一个使用功能选项创建新的House结构体的示例:
h := NewHouse(
WithConcrete(),
WithoutFireplace(),
)
在这里,NewHouse
是一个构造函数。 WithConcrete 和 WithFireplace 传递给构造函数的选项,以修改其返回值。
我们很快会看到为什么WithConcrete和WithFireplace被称为“函数”选项,以及它们比常规函数参数更有用的原因。
定义构造函数
首先,定义我们将为其设置选项的结构:
type House struct {
Material string
HasFireplace bool
Floors int
}
// `NewHouse`是一个用于`*House`的构造函数
func NewHouse() *House {
const (
defaultFloors = 2
defaultHasFireplace = true
defaultMaterial = "wood"
)
h := &House{
Material: defaultMaterial,
HasFireplace: defaultHasFireplace,
Floors: defaultFloors,
}
return h
}
House
可以由特定材料制成,可以有一定数量的楼层,并且可以选择包含壁炉。
NewHouse
构造函数给我们默认的House
指针,其所有属性都具有某些默认值。
通常,我们需要首先构造房子,然后如果需要不同的变体,则修改其值。使用功能选项,我们可以将一系列修改直接提供给构造函数。
定义功能选项
让我们定义一个接受指向 House
的指针的函数类型:
type HouseOption func(*House)
这是我们选项函数的签名。
让我们定义一些功能选项,可以修改 *House
实例:
func WithConcrete() HouseOption {
return func(h *House) {
h.Material = "concrete"
}
}
func WithoutFireplace() HouseOption {
return func(h *House) {
h.HasFireplace = false
}
}
上述每个函数都是“选项构造函数”并返回另一个函数,该函数以 *House
作为参数,并且不返回任何内容。
我们可以看到返回的函数修改了提供的 *House
实例。
我们甚至可以在选项构造器中添加参数,以修改返回的选项:
func WithFloors(floors int) HouseOption {
return func(h *House) {
h.Floors = floors
}
}
这将返回按 WithFloors
选项构造器给定的参数修改房屋中楼层数的选项。
将功能选项添加到我们的构造函数
现在,我们可以将功能选项合并到构造函数中:
// NewHouse现在将一个选项切片作为其余参数
func NewHouse(opts ...HouseOption) *House {
const (
defaultFloors = 2
defaultHasFireplace = true
defaultMaterial = "wood"
)
h := &House{
Material: defaultMaterial,
HasFireplace: defaultHasFireplace,
Floors: defaultFloors,
}
// 循环每个选项
for _, opt := range opts {
// 调用选项,通过实例化*House作为参数
opt(h)
}
// 返回修改后的house实例
return h
}
构造函数现在接受任意数量的函数选项参数列表,然后在返回之前将其应用于 *House
实例。回到第一个例子,我们现在可以理解这些选项的作用:
h := NewHouse(
WithConcrete(),
WithoutFireplace(),
WithFloors(3),
)
使用选项模式的优点
现在我们已经了解了如何实现选项模式,让我们看看为什么要使用函数选项。
显式
与以下方式修改 *House
不同:
h := NewHouse()
h.Material = "concrete"
我们可以在构造函数本身中明确建筑材料:
h := NewHouse(WithConcrete())
这有助于我们明确材料的字符串值。前面的示例允许用户制造拼写错误并暴露 *House
实例的内部。
可扩展
如果我们确实想给用户可扩展性,我们可以始终为我们的选项构造函数提供参数。
例如,由于我们的房屋楼层可以有任意数量,因此可以通过提供楼层数的参数来创建向房屋添加楼层的选项:
h := NewHouse(WithFloors(4))
参数的顺序
在使用功能选项时,选项的顺序不重要。与常规函数参数相比(必须以正确的顺序提供),这为我们提供了很大的灵活性。
此外,我们可以提供任意数量的选项。当使用带有常规参数的函数时,我们必须提供所有参数:
// 如果使用常规函数参数,则`NewHouse`看起来像什么
// 无论如何,我们总是需要提供所有三个参数
h := NewHouse("concrete", 5, true)
最后,祝您编码愉快!