go语言中的结构体嵌入详解(最新推荐)
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一、什么是结构体嵌入?</a></li><li><a href="#_label1">二、最直观的效果:字段“提升”(Promoted Fields)</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_0">1️⃣ 直接访问被嵌入结构体的字段</a></li></ul><li><a href="#_label2">三、方法也会被“提升”(非常关键)</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_1">1️⃣ 嵌入结构体的方法</a></li></ul><li><a href="#_label3">四、结构体嵌入 ≠ 继承,但很像</a></li><ul class="second_class_ul"></ul><li><a href="#_label4">五、嵌入多个结构体(类似多继承)</a></li><ul class="second_class_ul"></ul><li><a href="#_label5">六、字段 / 方法冲突规则(必考点)</a></li><ul class="second_class_ul"><li><a href="#_lab2_5_2">1️⃣ 同名字段冲突</a></li><li><a href="#_lab2_5_3">2️⃣ 子结构体“覆盖”嵌入结构体字段</a></li></ul><li><a href="#_label6">七、值嵌入 vs 指针嵌入(非常重要)</a></li><ul class="second_class_ul"><li><a href="#_lab2_6_4">1️⃣ 值嵌入</a></li><li><a href="#_lab2_6_5">2️⃣ 指针嵌入(推荐)</a></li></ul><li><a href="#_label7">八、嵌入结构体与接口的“神级配合”</a></li><ul class="second_class_ul"><li><a href="#_lab2_7_6">1️⃣ 嵌入结构体自动“继承”接口实现</a></li></ul><li><a href="#_label8">九、嵌入基础类型(不常用但要知道)</a></li><ul class="second_class_ul"></ul><li><a href="#_label9">十、结构体嵌入 vs 普通字段 对比</a></li><ul class="second_class_ul"></ul><li><a href="#_label10">十一、工程实践中的典型用法</a></li><ul class="second_class_ul"><li><a href="#_lab2_10_7">1️⃣ Web / 业务模型</a></li><li><a href="#_lab2_10_8">2️⃣ 装饰 / 组合模式</a></li></ul><li><a href="#_label11">十二、常见坑总结 ⚠️</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>一、什么是结构体嵌入?</h2><p><strong>结构体嵌入 = 在一个结构体中,直接写另一个结构体类型,不写字段名</strong></p>
<div class="jb51code"><pre class="brush:go;">type Base struct {
ID int
Name string
}
type User struct {
Base // 嵌入结构体(匿名字段)
Age int
}</pre></div>
<p>等价于(语义上):</p>
<div class="jb51code"><pre class="brush:go;">type User struct {
Base Base
Ageint
}
</pre></div>
<p><strong>但用法和能力完全不同</strong>(这是重点)。</p>
<p class="maodian"><a name="_label1"></a></p><h2>二、最直观的效果:字段“提升”(Promoted Fields)</h2>
<p class="maodian"><a name="_lab2_1_0"></a></p><h3>1️⃣ 直接访问被嵌入结构体的字段</h3>
<div class="jb51code"><pre class="brush:go;">u := User{
Base: Base{
ID: 1,
Name: "Tom",
},
Age: 18,
}
fmt.Println(u.ID) // 1
fmt.Println(u.Name) // Tom
fmt.Println(u.Base.ID) // 1(也可以)</pre></div>
<p>👉 <strong>Base 的字段被“提升”到了 User 上</strong></p>
<p class="maodian"><a name="_label2"></a></p><h2>三、方法也会被“提升”(非常关键)</h2>
<p class="maodian"><a name="_lab2_2_1"></a></p><h3>1️⃣ 嵌入结构体的方法</h3>
<div class="jb51code"><pre class="brush:go;">type Base struct{}
func (b Base) Hello() {
fmt.Println("hello from Base")
}
type User struct {
Base
}</pre></div>
<div class="jb51code"><pre class="brush:go;">var u User
u.Hello() // 等价于 u.Base.Hello()</pre></div>
<p>📌 <strong>字段 + 方法都会被提升</strong></p>
<p class="maodian"><a name="_label3"></a></p><h2>四、结构体嵌入 ≠ 继承,但很像</h2>
<p>Go 没有 <code>extends</code>,但结构体嵌入提供了:</p>
<table><thead><tr><th>能力</th><th>Go 结构体嵌入</th></tr></thead><tbody><tr><td>代码复用</td><td>✅</td></tr><tr><td>方法复用</td><td>✅</td></tr><tr><td>多继承</td><td>✅(可嵌入多个)</td></tr><tr><td>父子类型关系</td><td>❌(不是 is-a)</td></tr></tbody></table>
<p class="maodian"><a name="_label4"></a></p><h2>五、嵌入多个结构体(类似多继承)</h2>
<div class="jb51code"><pre class="brush:go;">type Logger struct{}
func (l Logger) Log() { fmt.Println("log") }
type Auth struct{}
func (a Auth) Check() { fmt.Println("auth") }
type Service struct {
Logger
Auth
}</pre></div>
<div class="jb51code"><pre class="brush:go;">s := Service{}
s.Log()
s.Check()
</pre></div>
<p>👉 Go <strong>允许多个嵌入结构体</strong></p>
<p class="maodian"><a name="_label5"></a></p><h2>六、字段 / 方法冲突规则(必考点)</h2>
<p class="maodian"><a name="_lab2_5_2"></a></p><h3>1️⃣ 同名字段冲突</h3>
<div class="jb51code"><pre class="brush:go;">type A struct {
Name string
}
type B struct {
Name string
}
type C struct {
A
B
}</pre></div>
<p>❌ 不能直接:</p>
<div class="jb51code"><pre class="brush:go;">c.Name // 编译错误:ambiguous selector
</pre></div>
<p>✅ 必须显式指定:</p>
<div class="jb51code"><pre class="brush:go;">c.A.Name
c.B.Name</pre></div>
<p class="maodian"><a name="_lab2_5_3"></a></p><h3>2️⃣ 子结构体“覆盖”嵌入结构体字段</h3>
<div class="jb51code"><pre class="brush:go;">type Base struct {
Name string
}
type User struct {
Base
Name string
}</pre></div>
<div class="jb51code"><pre class="brush:go;">u := User{}
u.Name = "UserName" // 访问的是 User.Name
u.Base.Name = "BaseName"
</pre></div>
<p>📌 <strong>就近原则</strong>:优先访问最外层字段</p>
<p class="maodian"><a name="_label6"></a></p><h2>七、值嵌入 vs 指针嵌入(非常重要)</h2>
<p class="maodian"><a name="_lab2_6_4"></a></p><h3>1️⃣ 值嵌入</h3>
<div class="jb51code"><pre class="brush:go;">type User struct {
Base
}
</pre></div>
<ul><li>会 <strong>拷贝一份 Base</strong></li><li>修改时改的是副本</li></ul>
<p class="maodian"><a name="_lab2_6_5"></a></p><h3>2️⃣ 指针嵌入(推荐)</h3>
<div class="jb51code"><pre class="brush:go;">type User struct {
*Base
}
</pre></div>
<div class="jb51code"><pre class="brush:go;">b := &Base{ID: 1}
u := User{Base: b}
u.ID = 2
fmt.Println(b.ID) // 2</pre></div>
<p>✅ <strong>共享同一份数据</strong><br />✅ 常用于组合、组件设计</p>
<p class="maodian"><a name="_label7"></a></p><h2>八、嵌入结构体与接口的“神级配合”</h2>
<p>这是 Go 非常核心的设计思想。</p>
<p class="maodian"><a name="_lab2_7_6"></a></p><h3>1️⃣ 嵌入结构体自动“继承”接口实现</h3>
<div class="jb51code"><pre class="brush:go;">type Speaker interface {
Speak()
}
type Human struct{}
func (h Human) Speak() {
fmt.Println("human speak")
}
type Student struct {
Human
}</pre></div>
<div class="jb51code"><pre class="brush:go;">var s Speaker = Student{} // ✅ Student 自动实现 Speaker
s.Speak()
</pre></div>
<p>📌 <strong>只要嵌入的结构体实现了接口,外层结构体也实现了接口</strong></p>
<p class="maodian"><a name="_label8"></a></p><h2>九、嵌入基础类型(不常用但要知道)</h2>
<div class="jb51code"><pre class="brush:go;">type MyInt int
type Counter struct {
MyInt
}</pre></div>
<div class="jb51code"><pre class="brush:go;">c := Counter{}
c.MyInt = 10
</pre></div>
<p>⚠️ 不如结构体嵌入常见,但合法</p>
<p class="maodian"><a name="_label9"></a></p><h2>十、结构体嵌入 vs 普通字段 对比</h2>
<table><thead><tr><th>对比项</th><th>嵌入结构体</th><th>普通字段</th></tr></thead><tbody><tr><td>访问方式</td><td>u.ID</td><td>u.Base.ID</td></tr><tr><td>方法提升</td><td>✅</td><td>❌</td></tr><tr><td>接口继承</td><td>✅</td><td>❌</td></tr><tr><td>语义</td><td>组合</td><td>拥有</td></tr></tbody></table>
<p>📌 <strong>推荐原则</strong>:<br />👉 <strong>想复用行为(方法) → 用嵌入</strong><br />👉 <strong>只是数据成员 → 用普通字段</strong></p>
<p class="maodian"><a name="_label10"></a></p><h2>十一、工程实践中的典型用法</h2>
<p class="maodian"><a name="_lab2_10_7"></a></p><h3>1️⃣ Web / 业务模型</h3>
<div class="jb51code"><pre class="brush:go;">type Model struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
}
type Order struct {
Model
OrderNo string
}</pre></div>
<p>(GORM 就大量用这个模式)</p>
<p class="maodian"><a name="_lab2_10_8"></a></p><h3>2️⃣ 装饰 / 组合模式</h3>
<div class="jb51code"><pre class="brush:go;">type Service struct {
*Logger
*DB
}
</pre></div>
<p class="maodian"><a name="_label11"></a></p><h2>十二、常见坑总结 ⚠️</h2>
<ul><li>❌ <strong>以为是继承</strong>(不是)</li><li>❌ 字段冲突没处理</li><li>❌ 不理解指针嵌入 vs 值嵌入</li><li>❌ 滥用嵌入(嵌太多层可读性差)</li></ul>
頁:
[1]