在 Go 语言中,如果要封装一个对象,首先需要定义一个新的类型(结构体),其次是提供一个用于为此类型初始化的函数,代码如下:
type Student struct {
Name string
Age int
Sex bool
Address string
Email string
}
func New(name string, age int, sex bool) *Student {
return &Student{name, age, sex, "湖南省", "1314@qq.com"}
}
// 调用New函数获取Student实例
stu := New("aitao", 100, true)
此时,初始化函数只为部分字段提供了初始化操作,其它字段都是默认值,那么要为这些可选字段提供初始化操作该如何实现呢?
🚨 需要注意的一点,在 Go 语言中是不支持函数重载机制的,即只要是在同一个包中就不允许存在相同名称的函数!!!
下面是一种比较简单的写法,代码如下:
type Student struct {
Name string
Age int
Sex bool
// 组合 Options 类型
Options *Options
}
// 定义可选参数类型
type Options struct {
Address string
Email string
}
func New(name string, age int, sex bool, options *Options) *Student {
if options != nil {
if options.Email != "" {
options.Email = "湖南省"
}
if options.Address != "" {
options.Address = "1314@qq.com"
}
}
return &Student{name, age, sex, options}
}
上述代码提供一个Options类型来为可选字段提供初始化功能,在New函数中依次校验可选字段,如果用户未提供了可选字段值,初始化函数会提供默认值。
那么问题来了,如果可选字段有很多,那不是得一个一个校验?怎么解决呢?有两种方法,一种是使用闭包,另一种是使用接口。
闭包实现
采用闭包方式在创建对象时传递可选参数,分为以下几个步骤:
1.明确需要初始化的对象(Student);
type Student struct {
Name string
Age int
Sex bool
Address string
Email string
}
2.定义选项函数类型(Options);
type Options func(*Student)
3.实现具体的选项函数,这里的可选参数只有两个,因此只需要定义两个选项函数;
func WithEmail(email string) Options {
return func(student *Student) {
student.Email = email
}
}
func WithAddress(address string) Options {
return func(student *Student) {
student.Address = address
}
}
4.重写对象初始化函数;
func New(name string, age int, sex bool, options ...Options) *Student {
sPtr := &Student{name, age, sex, "湖南省", "1314@qq.com"}
// 遍历可选参数
for _, option := range options {
option(sPtr) // 调用对应的闭包函数赋值
}
return sPtr
}
5.调用对象初始化函数: 在调用构造函数时,可以传递必需的字段和任意数量的选项接口来定制结构体实例。
// 调用 New 函数获取 Student 实例
stu1 := New("aitao", 100, true)
// 调用选项函数为指定可选参数初始化值
stu2 = New("aitao", 100, true, WithEmail("1314@qq.com"))
stu3 := New("aitao", 100, true, WithEmail("1314@qq.com"), WithAddress("北京市"))
优点1.灵活性高:闭包方式允许在运行时动态地创建和组合选项函数。这使得选项逻辑可以更加灵活和复杂,适用于需要动态配置的场景。
2.代码简洁: 闭包方式的代码通常比接口方法更简洁。每个选项函数都是一个简单的闭包,不需要定义额外的类型和方法。
3.易组合:闭包方式可以轻松地组合多个选项函数,形成复杂的选项逻辑。例如,可以通过组合多个闭包函数来实现复杂的初始化逻辑。
缺点1.类型安全差:闭包方式的类型安全性不如接口方法。由于闭包函数是动态创建的,编译器无法在编译时检查每个闭包函数的类型和行为。
2.可读性差: 闭包方式的代码可读性可能不如接口方法。尤其是在选项函数较多或逻辑较复杂的情况下,闭包方式的代码可能会显得较为混乱。
接口实现
采用接口方式在创建对象时传递可选参数,分为以下几个步骤: 1.明确需要初始化的对象(Student); 2.定义选项接口(Options);
type Options interface {
apply(*Student)
}
3.实现具体选项类型: 需要为每个可选字段实现一个具体的选项类型,并实现 Options 接口的 apply 方法。每个选项类型都是一个自定义类型,通常是对基本类型的封装。
// 针对 Email 字段实现一个选择类型
type emailOption string
func (eo emailOption) apply(s *Student) {
s.Email = string(eo)
}
func WithEmail(email string) Options {
return emailOption(email)
}
// 针对 Address 字段实现一个选择类型
type addressOption string
func (ao addressOption) apply(s *Student) {
s.Address = string(ao)
}
func WithAddress(address string) Options {
return addressOption(address)
}
4.重写对象初始化函数;
func New(name string, age int, sex bool, options ...Options) *Student {
stu := &Student{name, age, sex, "湖南省", "1314@qq.com"}
// 遍历可选参数
for _, option := range options {
option.apply(stu) // 调用 Options 接口对应方法
}
return stu
}
5.调用对象初始化函数: 在调用构造函数时,可以传递必需的字段和任意数量的选项接口来定制结构体实例。
// 调用 New 函数获取 Student 实例
stu1 := New("aitao", 100, true)
// 调用选项函数为指定可选参数初始化值
stu2 = New("aitao", 100, true, WithEmail("1314@qq.com"))
stu3 := New("aitao", 100, true, WithEmail("1314@qq.com"), WithAddress("北京市"))
优点1.类型安全:使用接口方法实现选项模式时,每个选项类型都必须实现
Options接口的apply方法。这确保了每个选项类型都符合预期的行为,提供了更好的类型安全性。2.扩展性:通过接口,可以轻松地添加新的选项类型,而不需要修改现有的代码。只需定义一个新的类型并实现
Options接口即可。3.代码清晰: 接口方法的实现方式使得代码结构更加清晰,每个选项类型的职责明确,易于理解和维护。
缺点1.类型转换:每个选项类型都需要定义一个自定义类型,并实现
apply方法。这可能会导致代码量增加,尤其是在选项较多的情况下。2.灵活性差:接口方法的实现方式相对固定,不如闭包方式灵活。例如,闭包方式可以通过组合多个闭包函数来实现复杂的选项逻辑,而接口方法则需要定义更多的类型和方法。