前言

在golang中对某个结构体进行初始化时会创建一个函数,一般情况下该函数取名为NewXXXX()

当结构体多了之后,对其进行实例化各种New眼花缭乱,使得代码可读性降低,而golang在1.18版本推出了泛型之后

可以使用泛型模拟一个Default构造器

1. 实现方法

 // Defaulter 接口定义了 InitDefault 方法,用于初始化结构体
 type Defaulter interface {
     InitDefault(args ...any)
 }
 ​
 // defaulterPtr 类型约束,要求类型 T 的指针实现 Defaulter 接口
 type defaulterPtr[T any] interface {
     *T
     Defaulter
 }
 ​
 // Default 函数使用泛型创建并初始化一个类型为 T 的对象
 // 参数 args 可变,传递给 InitDefault 方法
 func Default[T any, P defaulterPtr[T]](args ...any) T {
     var v T                    // 创建一个类型为 T 的变量
     P.InitDefault(&v, args...) // 使用类型 T 的指针调用 InitDefault 方法进行初始化
     return v                   // 返回初始化后的变量
 }

2. 使用方法

 type Cat struct {
     Name string
 }
 ​
 // Cat 实现了 Defaulter 接口的 InitDefault 方法
 // 方法使用传入的参数进行初始化
 // 此方法可以视作构造函数,所有结构体只要实现了此方法,既可使用Default进行构造
 func (c *Cat) InitDefault(args ...any) {
     c.Name = args[0].(string) // 将第一个参数转换为字符串并赋值给 Name 字段
 }
 ​
 // Cat 的方法,输出猫的名字
 func (c *Cat) Say() {
     fmt.Printf("My name is %s\n", c.Name)
 }
 ​
 func main() {
     tom := Default[Cat]("Tom")
     tom.Say()
 ​
     jerry := Default[Cat]("Jerry")
     jerry.Say()
 }
 ​
 /********************
 *   输出结果
 *
 * My name is Tom
 * My name is Jerry
 *
 ********************/
 ​

3. 为什么这样写?

  1. 实现通用初始化逻辑: 通过定义一个通用的 Default 函数,可以避免为每个类型都编写重复的初始化逻辑。例如,初始化 Cat 对象时,只需要实现 InitDefault 方法,而不需要编写特定的构造函数。

  2. 利用泛型提高代码复用性: Go 1.18 引入了泛型,通过泛型可以编写更加通用和复用的代码。Default 函数就是一个很好的例子,它利用泛型和类型约束,实现了对任意类型的通用初始化。

  3. 保证类型安全: 使用类型约束 defaulterPtr[T] 确保传入的类型 T 的指针实现了 Defaulter 接口,这样在编译时就能保证类型安全,避免运行时错误。

  4. 可扩展性: 通过接口 Defaulter,可以很容易地扩展新的类型。只需要实现 InitDefault 方法,就可以通过 Default 函数进行初始化,而不需要修改现有代码。