Golang中何时使用类型嵌入?
Golang中何时使用类型嵌入? 目前,我有时在DTO中会遇到需要嵌入类型(另一个DTO)的情况,就像特质那样。
我想他指的就是这个。因此,除非是为了构建扁平结构体(不含方法),否则很少建议嵌入类型。
是的,这个观点非常好。我尤其在对那些包含公共字段的结构体进行JSON(反)序列化时这样做过。
更多关于Golang中何时使用类型嵌入?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
然而,这是我能想到的唯一做过这种情况的场景。通常我最终会忘记嵌入的函数,然后不小心做了类似这样的事情:
func (f TempFile) Read(p []byte) (n int, err error) {
// ...
nn, err := self.Read(p) // 糟糕,我本意是想调用 self.File.Read...
// ...
}
这错误相当明显,但我还是不止一次地犯了同样的错误。我想我可能就是不擅长处理这种类似继承的嵌入方式 😵。
这让我想起了 Bitfield 博客中的一句话,出自这篇文章 https://bitfieldconsulting.com/golang/commandments::
不要嵌入结构体类型,让它们神奇地获得不可见的方法。
我想他指的就是这种情况。因此,除非是为了构建扁平的结构体(没有方法),否则很少建议嵌入类型。
如果你熟悉其他支持继承的语言(如Java、Python、C++、C#等)。例如,我在一些包中有一个简单的TempFile实现,本质上如下所示:
type TempFile struct {
*os.File
}
func (f TempFile) Close() error {
if err := f.try("close", func(f TempFile) error {
return f.File.Close()
}); err != nil {
return err
}
return f.try("remove", func(f TempFile) error {
return os.Remove(f.File.Name())
})
}
func (f TempFile) try(fn func(TempFile) error) error {
if err := fn(f); err != nil {
return fmt.Errorf(
"error attempting to %s file %v: %w",
what, f.Name(), err,
)
}
return nil
}
因此,本质上我的TempFile类型与*os.File的工作方式相同,只是在调用Close时,它会调用“基类”*os.File的Close函数,然后删除文件。如果*os.File将来获得新方法,我的TempFile将“继承”它。
然而,这是我唯一能想到的使用了这种模式的情况。通常我最终会忘记嵌入的函数,然后不小心写出这样的代码:
func (f TempFile) Read(p []byte) (n int, err error) {
// ...
nn, err := self.Read(p) // 糟糕,我本意是调用 self.File.Read...
// ...
}
这错误相当明显,但我却不止一次地犯同样的错误。我想我可能不太适合这种类似继承的嵌入方式。
在Golang中,类型嵌入通常用于以下场景:
- 代码复用:当多个结构体需要共享相同的字段和方法时
- 接口组合:通过嵌入接口来扩展接口功能
- 模拟继承:虽然Go没有传统继承,但嵌入可以实现类似效果
对于DTO场景,类型嵌入特别有用:
// 基础DTO
type BaseDTO struct {
ID int `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// 用户DTO嵌入基础DTO
type UserDTO struct {
BaseDTO // 嵌入类型
Username string `json:"username"`
Email string `json:"email"`
}
// 产品DTO也嵌入相同的基础DTO
type ProductDTO struct {
BaseDTO // 嵌入类型
Name string `json:"name"`
Price float64 `json:"price"`
}
func main() {
user := UserDTO{
BaseDTO: BaseDTO{
ID: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
Username: "john_doe",
Email: "john@example.com",
}
// 可以直接访问嵌入字段
fmt.Printf("User ID: %d\n", user.ID)
fmt.Printf("Username: %s\n", user.Username)
}
类型嵌入也常用于接口组合:
type Reader interface {
Read() string
}
type Writer interface {
Write(string)
}
// 通过嵌入组合接口
type ReadWriter interface {
Reader
Writer
}
type File struct {
content string
}
func (f *File) Read() string {
return f.content
}
func (f *File) Write(s string) {
f.content = s
}
func process(rw ReadWriter) {
rw.Write("data")
fmt.Println(rw.Read())
}
在DTO中使用类型嵌入时需要注意:
- 嵌入类型的方法会提升到外层结构体
- 字段访问是透明的,可以直接访问
- 如果有字段名冲突,需要显式指定嵌入类型名
- JSON序列化时,嵌入字段会平铺展开

