---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: Go 接口与结构体嵌入:与类继承的区别及组合式设计
description: 比较 Java/C++ 类继承与 Go 接口的结构化类型(structural typing)之间的差异,并通过代码示例说明利用结构体嵌入(struct embedding)和方法委托(method delegation)实现的组合(Composition)模式。
language: zh
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# 概述

Go 接口使得多个结构体能够轻松拥有具有相同参数和返回值的函数，但这与 Java 中使用 extends 关键字来适当扩展和覆盖内部函数行为的方式并不相同。只有正确理解 Go 的组合式代码复用，才能避免与继承混淆，但从一开始就在理论上完全理解它是很困难的。让我们结合容易出错的场景来一起学习。

## 常见错误

初学者可能会犯以下错误。

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type Apple struct {
	Label string
	Name  string
	Brix  float64
}

type Watermelon struct {
	Label string
	Name  string
	Brix  float64
}

func (a *Apple) PrintAll() {
	fmt.Printf("Fruit: %s, Label: %s, Brix: %v\n", a.Name, a.Label, a.Brix)
}

const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Brix 	= 14.5;
	a.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Brix = 10;
	w.Name = "watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
}

func main() {
	fmt.Println("Inheritance test #1")
	apple := new(Apple)
	watermelon := apple
	apple.SetLabel("Apple_1")
	fmt.Println("Apple, before copied to Watermelon")
	apple.PrintAll()
	watermelon.SetLabel("WaterMelon_2")
	fmt.Println("Apple, after copied to Watermelon")
	apple.PrintAll()
	fmt.Println("Watermelon, which inherited Apple's Method")
	watermelon.PrintAll()
}
```

如果认为 Go 遵循传统继承，这段代码看起来似乎没有问题。但是它的输出结果如下。

```
Inheritance test #1
Succeed: Label was  Apple_1
Apple, before copied to Watermelon
Fruit: apple, Label: Apple_1, Brix: 14.5
Failed: Label was  WaterMelon_2
Apple, after copied to Watermelon
Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
Watermelon, which inherited Apple's Method
Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
```

这里 Go 的行为只是变得更加清楚了。

```go
watermelon := apple
```

这段代码完全没有把 Apple 转换为 Watermelon 类。
watermelon 只是指向 apple 的指针而已。

在此再次强调，**Go 不遵循传统的继承概念。**

如果在存在这种误解的情况下编写代码，就会产生无意义的指针创建、意外地为其他结构体复制函数等严重错误。

那么，良好的示例代码应该是什么样的呢？

## Go 语言中的适当示例

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type BaseFruit struct {
	Name  string
	Brix  float64
}

type Apple struct {
	Label string
	Fruit BaseFruit
}

type Watermelon struct {
	Label string
	Fruit BaseFruit

}

func (b *BaseFruit) PrintAll() {
	fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
}


const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Fruit.Brix 	= 14.5;
	a.Fruit.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Fruit.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", a.Fruit.Name, a.Label);
	a.Fruit.PrintAll()
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Fruit.Brix = 10;
	w.Fruit.Name = "Watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Fruit.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", w.Fruit.Name, w.Label);
	w.Fruit.PrintAll()
}

func main() {
	apple := new(Apple)
	watermelon := new(Watermelon)
	apple.SetLabel("Apple_1")
	watermelon.SetLabel("WaterMelon_2")
}
```

不过，在 Go 中也可以让它**看起来像**继承。
这就是匿名嵌入的例子。如果将内部结构体声明为无名结构体，就可以实现这一点。
在这种情况下，**即使不明确指定，也可以直接访问下级结构体的字段**。
利用这种将下级结构体的字段**提升**到上级结构体的模式，在某些情况下可以提高可读性。但是，在需要明确展示下级结构体的情况下，建议不要使用。

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type BaseFruit struct {
	Name  string
	Brix  float64
}

type Apple struct {
	Label string
	BaseFruit
}

type Watermelon struct {
	Label string
	BaseFruit

}

func (b *BaseFruit) PrintAll() {
	fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
}


const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Brix 	= 14.5;
	a.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", a.Name, a.Label);
	a.PrintAll()
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Brix = 10;
	w.Name = "Watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", w.Name, w.Label);
	w.PrintAll()
}

func main() {
	apple := new(Apple)
	watermelon := new(Watermelon)
	apple.SetLabel("Apple_1")
	watermelon.SetLabel("WaterMelon_2")
}
```

在这个示例中存在以下差异。

```go
w.PrintAll() // 不是 w.Friut.PrintAll()，而是通过无名结构体进行的自动提升调用
```

两个示例的重要之处如下。

- main 保持简洁，函数按功能划分
- 不同的结构体使用不同的对象
- 需要共享时使用内部结构体

这种编程理念有什么好处呢？

## 优点

- 清楚地区分需要共享的方法和不需要共享的方法
- 将责任归属分离到各个结构体和方法中
- 根据所需功能规范，按结构进行分离的代码

刚开始时，Go 语言与传统 OOP 不同，可能会感到不习惯，但熟悉之后就能够进行显式的编程。

## 总结

- 隔离责任归属
- 按结构体细致地划分
- 不应将方法理解为 Java 中的抽象类
- 进行显式且具体的编程
  Go 语言比传统 OOP 模型更加简洁明了，应当单独处理。与其进行扩展式编程，不如分阶段、有结构地进行分离编写。
