go-interfaces

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Interfaces and Composition

Go接口与类型组合

Go's interfaces enable flexible, decoupled designs through implicit satisfaction and composition. This skill covers interface fundamentals, type inspection, and Go's approach to composition over inheritance.
Source: Effective Go

Go的接口通过隐式实现和类型组合,支持灵活、解耦的设计。本文涵盖接口基础、类型检查,以及Go语言优先使用组合而非继承的实现方式。
来源Effective Go

Interface Basics

接口基础

Interfaces in Go specify behavior: if something can do this, it can be used here. Types implement interfaces implicitly—no
implements
keyword needed.
go
// io.Writer interface - any type with this method satisfies it
type Writer interface {
    Write(p []byte) (n int, err error)
}
A type satisfies an interface by implementing its methods:
go
type ByteSlice []byte

// ByteSlice now implements io.Writer
func (p *ByteSlice) Write(data []byte) (n int, err error) {
    *p = append(*p, data...)
    return len(data), nil
}

// Can be used anywhere io.Writer is expected
var w io.Writer = &ByteSlice{}
fmt.Fprintf(w, "Hello, %s", "World")
Go中的接口用于定义行为:只要某个类型能完成这件事,它就能在此处被使用。类型会隐式实现接口——无需
implements
关键字。
go
// io.Writer interface - any type with this method satisfies it
type Writer interface {
    Write(p []byte) (n int, err error)
}
类型只需实现接口的方法,即视为实现了该接口:
go
type ByteSlice []byte

// ByteSlice now implements io.Writer
func (p *ByteSlice) Write(data []byte) (n int, err error) {
    *p = append(*p, data...)
    return len(data), nil
}

// Can be used anywhere io.Writer is expected
var w io.Writer = &ByteSlice{}
fmt.Fprintf(w, "Hello, %s", "World")

Multiple Interface Implementation

多接口实现

A type can implement multiple interfaces simultaneously:
go
type Sequence []int

// Implements sort.Interface
func (s Sequence) Len() int           { return len(s) }
func (s Sequence) Less(i, j int) bool { return s[i] < s[j] }
func (s Sequence) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// Implements fmt.Stringer
func (s Sequence) String() string {
    sort.Sort(s)
    return fmt.Sprint([]int(s))
}
一个类型可同时实现多个接口:
go
type Sequence []int

// Implements sort.Interface
func (s Sequence) Len() int           { return len(s) }
func (s Sequence) Less(i, j int) bool { return s[i] < s[j] }
func (s Sequence) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

// Implements fmt.Stringer
func (s Sequence) String() string {
    sort.Sort(s)
    return fmt.Sprint([]int(s))
}

Interface Naming

接口命名

By convention, one-method interfaces use the method name plus
-er
suffix:
Reader
,
Writer
,
Formatter
,
Stringer
.

按照惯例,单方法接口采用“方法名+-er”后缀命名:
Reader
Writer
Formatter
Stringer

Type Assertions

类型断言

A type assertion extracts the concrete value from an interface.
类型断言用于从接口中提取具体值。

Basic Syntax

基础语法

go
value.(typeName)
The result has the static type
typeName
. The type must be either:
  • The concrete type held by the interface, or
  • Another interface type the value can be converted to
go
var w io.Writer = os.Stdout
f := w.(*os.File)  // Extract *os.File from io.Writer
go
value.(typeName)
结果的静态类型为
typeName
。该类型必须是:
  • 接口当前持有的具体类型,或者
  • 该值可转换为的另一个接口类型
go
var w io.Writer = os.Stdout
f := w.(*os.File)  // Extract *os.File from io.Writer

Comma-Ok Idiom

逗号-ok惯用法

Without checking, a failed assertion causes a runtime panic. Use the comma-ok idiom to test safely:
go
str, ok := value.(string)
if ok {
    fmt.Printf("string value is: %q\n", str)
} else {
    fmt.Printf("value is not a string\n")
}
If the assertion fails,
str
is the zero value (empty string) and
ok
is false.
若不做检查,失败的断言会导致运行时恐慌。使用逗号-ok惯用法可安全地进行测试:
go
str, ok := value.(string)
if ok {
    fmt.Printf("string value is: %q\n", str)
} else {
    fmt.Printf("value is not a string\n")
}
如果断言失败,
str
会是零值(空字符串),
ok
为false。

Checking Interface Implementation

检查接口实现

To check if a value implements an interface without using the result:
go
if _, ok := val.(json.Marshaler); ok {
    fmt.Printf("value %v implements json.Marshaler\n", val)
}

若只需检查值是否实现了接口,无需使用结果:
go
if _, ok := val.(json.Marshaler); ok {
    fmt.Printf("value %v implements json.Marshaler\n", val)
}

Type Switch

类型切换

A type switch discovers the dynamic type of an interface variable using the
.(type)
syntax:
go
var t interface{}
t = functionOfSomeType()

switch t := t.(type) {
default:
    fmt.Printf("unexpected type %T\n", t)
case bool:
    fmt.Printf("boolean %t\n", t)             // t has type bool
case int:
    fmt.Printf("integer %d\n", t)             // t has type int
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}
类型切换通过
.(type)
语法来发现接口变量的动态类型:
go
var t interface{}
t = functionOfSomeType()

switch t := t.(type) {
default:
    fmt.Printf("unexpected type %T\n", t)
case bool:
    fmt.Printf("boolean %t\n", t)             // t has type bool
case int:
    fmt.Printf("integer %d\n", t)             // t has type int
case *bool:
    fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
case *int:
    fmt.Printf("pointer to integer %d\n", *t) // t has type *int
}

Idiomatic Reuse of Variable Name

复用变量名的惯用写法

It's idiomatic to reuse the name in the switch expression. This declares a new variable with the same name but the correct type in each case branch.
在切换表达式中复用变量名是惯用写法。这会在每个case分支中声明一个同名但类型正确的新变量。

Mixing Concrete and Interface Types

混合具体类型与接口类型

Type switches can match both concrete types and interface types:
go
type Stringer interface {
    String() string
}

var value interface{}
switch str := value.(type) {
case string:
    return str                // str is string
case Stringer:
    return str.String()       // str is Stringer
}

类型切换可同时匹配具体类型和接口类型:
go
type Stringer interface {
    String() string
}

var value interface{}
switch str := value.(type) {
case string:
    return str                // str is string
case Stringer:
    return str.String()       // str is Stringer
}

Generality

通用性

If a type exists only to implement an interface with no exported methods beyond that interface, don't export the type—return the interface from constructors.
如果某个类型仅为实现接口而存在,且除该接口外无其他导出方法,则不要导出该类型——从构造函数返回接口即可。

Hide Implementation, Expose Interface

隐藏实现,暴露接口

go
// Good: Constructor returns interface type
func NewHash() hash.Hash32 {
    return &myHash{}  // unexported type
}

// The implementation is hidden; callers only see hash.Hash32
go
// Good: Constructor returns interface type
func NewHash() hash.Hash32 {
    return &myHash{}  // unexported type
}

// The implementation is hidden; callers only see hash.Hash32

Real-World Example: crypto/cipher

实际案例:crypto/cipher

The
crypto/cipher
package demonstrates this pattern:
go
type Block interface {
    BlockSize() int
    Encrypt(dst, src []byte)
    Decrypt(dst, src []byte)
}

type Stream interface {
    XORKeyStream(dst, src []byte)
}

// Returns Stream interface, hiding implementation details
func NewCTR(block Block, iv []byte) Stream
Benefits:
  • Implementation can change without affecting callers
  • Substituting algorithms requires only changing the constructor call
  • Documentation lives on the interface, not repeated on each implementation

crypto/cipher
包展示了该模式:
go
type Block interface {
    BlockSize() int
    Encrypt(dst, src []byte)
    Decrypt(dst, src []byte)
}

type Stream interface {
    XORKeyStream(dst, src []byte)
}

// Returns Stream interface, hiding implementation details
func NewCTR(block Block, iv []byte) Stream
优势
  • 实现可在不影响调用者的情况下变更
  • 替换算法只需修改构造函数调用
  • 文档集中在接口上,无需在每个实现中重复

Interface Embedding

接口嵌入

Combine interfaces by embedding them:
go
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter combines Reader and Writer
type ReadWriter interface {
    Reader
    Writer
}
A
ReadWriter
can do what a
Reader
does and what a
Writer
does—it's a union of the embedded interfaces.
Rule: Only interfaces can be embedded within interfaces.

可通过嵌入来组合多个接口:
go
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// ReadWriter combines Reader and Writer
type ReadWriter interface {
    Reader
    Writer
}
ReadWriter
可完成
Reader
Writer
的所有操作——它是嵌入接口的并集。
规则:只有接口可嵌入到接口中。

Struct Embedding

结构体嵌入

Go uses embedding for composition instead of inheritance. Embedding promotes methods from the inner type to the outer type.
Go使用嵌入实现组合而非继承。嵌入会将内部类型的方法提升到外部类型中。

Basic Struct Embedding

基础结构体嵌入

go
// bufio.ReadWriter embeds Reader and Writer
type ReadWriter struct {
    *Reader  // *bufio.Reader
    *Writer  // *bufio.Writer
}
Without embedding, you'd need forwarding methods:
go
// Without embedding - tedious boilerplate
type ReadWriter struct {
    reader *Reader
    writer *Writer
}

func (rw *ReadWriter) Read(p []byte) (n int, err error) {
    return rw.reader.Read(p)
}
With embedding, methods are promoted automatically.
bufio.ReadWriter
satisfies
io.Reader
,
io.Writer
, and
io.ReadWriter
without explicit forwarding.
go
// bufio.ReadWriter embeds Reader and Writer
type ReadWriter struct {
    *Reader  // *bufio.Reader
    *Writer  // *bufio.Writer
}
若不使用嵌入,需编写转发方法:
go
// Without embedding - tedious boilerplate
type ReadWriter struct {
    reader *Reader
    writer *Writer
}

func (rw *ReadWriter) Read(p []byte) (n int, err error) {
    return rw.reader.Read(p)
}
使用嵌入后,方法会自动被提升。
bufio.ReadWriter
无需显式转发即可满足
io.Reader
io.Writer
io.ReadWriter
接口。

Embedding for Convenience

嵌入的便利性

Mix embedded and named fields:
go
type Job struct {
    Command string
    *log.Logger
}

// Job now has Print, Printf, Println methods
job.Println("starting now...")
可混合嵌入字段和命名字段:
go
type Job struct {
    Command string
    *log.Logger
}

// Job now has Print, Printf, Println methods
job.Println("starting now...")

Accessing Embedded Fields

访问嵌入字段

The type name (without package qualifier) serves as the field name:
go
// Access the embedded Logger directly
job.Logger.SetPrefix("Job: ")
类型名(不带包限定符)可作为字段名:
go
// Access the embedded Logger directly
job.Logger.SetPrefix("Job: ")

Method Overriding

方法重写

Define a method on the outer type to override the embedded method:
go
func (job *Job) Printf(format string, args ...interface{}) {
    job.Logger.Printf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}
在外部类型上定义方法可覆盖嵌入类型的方法:
go
func (job *Job) Printf(format string, args ...interface{}) {
    job.Logger.Printf("%q: %s", job.Command, fmt.Sprintf(format, args...))
}

Embedding vs Subclassing

嵌入与子类化的区别

Key difference: When an embedded method is invoked, the receiver is the inner type, not the outer one. The embedded type doesn't know it's embedded.
go
type ReadWriter struct {
    *Reader
    *Writer
}

// When rw.Read() is called, the receiver is the Reader, not ReadWriter
核心差异:调用嵌入方法时,接收者是内部类型而非外部类型。嵌入类型并不知道自己被嵌入。
go
type ReadWriter struct {
    *Reader
    *Writer
}

// When rw.Read() is called, the receiver is the Reader, not ReadWriter

Name Conflict Resolution

名称冲突解决

  1. Outer fields/methods hide inner ones: A field
    X
    on the outer type hides any
    X
    in embedded types
  2. Same level conflicts are errors: Embedding two types with the same method name at the same level causes an error (unless never accessed)

  1. 外部字段/方法覆盖内部字段/方法:外部类型的字段
    X
    会覆盖嵌入类型中的所有
    X
  2. 同层级冲突会报错:嵌入两个在同一层级有同名方法的类型会导致错误(除非从未访问该方法)

Interface Satisfaction Checks

接口实现校验

Most interface conversions are checked at compile time. But sometimes you need to verify implementation explicitly.
大多数接口转换会在编译时检查。但有时需要显式验证实现。

Compile-Time Interface Check

编译时接口检查

Use a blank identifier assignment to verify a type implements an interface:
go
// Verify *RawMessage implements json.Marshaler at compile time
var _ json.Marshaler = (*RawMessage)(nil)
This causes a compile error if
*RawMessage
doesn't implement
json.Marshaler
.
使用空白标识符赋值来验证类型是否实现了接口:
go
// Verify *RawMessage implements json.Marshaler at compile time
var _ json.Marshaler = (*RawMessage)(nil)
如果
*RawMessage
未实现
json.Marshaler
,会导致编译错误。

When to Use This Pattern

使用场景

Use compile-time checks when:
  • There are no static conversions that would verify the interface automatically
  • The type must satisfy an interface for correct behavior (e.g., custom JSON marshaling)
  • Interface changes should break compilation, not silently degrade
go
// In your package
type MyType struct { /* ... */ }

func (m *MyType) MarshalJSON() ([]byte, error) { /* ... */ }

// Compile-time check - fails if MarshalJSON signature is wrong
var _ json.Marshaler = (*MyType)(nil)
Don't add these checks for every interface implementation—only when there's no other static conversion that would catch the error.

在以下场景使用编译时检查:
  • 没有静态转换可自动验证接口时
  • 类型必须实现接口才能保证行为正确(如自定义JSON序列化)
  • 接口变更应导致编译失败,而非静默降级
go
// In your package
type MyType struct { /* ... */ }

func (m *MyType) MarshalJSON() ([]byte, error) { /* ... */ }

// Compile-time check - fails if MarshalJSON signature is wrong
var _ json.Marshaler = (*MyType)(nil)
不要为每个接口实现都添加此类检查——仅当没有其他静态转换能捕获错误时使用。

Receiver Type

接收器类型

Advisory: Go Wiki CodeReviewComments
Choosing whether to use a value or pointer receiver on methods can be difficult. If in doubt, use a pointer, but there are times when a value receiver makes sense.
建议:Go Wiki CodeReviewComments
选择值接收器还是指针接收器可能会比较困难。若有疑问,使用指针接收器,但某些场景下值接收器更合适。

When to Use Pointer Receiver

使用指针接收器的场景

  • Method mutates receiver: The receiver must be a pointer
  • Receiver contains sync.Mutex or similar: Must be a pointer to avoid copying
  • Large struct or array: A pointer receiver is more efficient. If passing all elements as arguments feels too large, it's too large for a value receiver
  • Concurrent or called methods might mutate: If changes must be visible in the original receiver, it must be a pointer
  • Elements are pointers to something mutating: Prefer pointer receiver to make the intention clearer
  • 方法会修改接收器:必须使用指针接收器
  • 接收器包含sync.Mutex或类似类型:必须使用指针以避免拷贝
  • 大型结构体或数组:指针接收器更高效。如果将所有元素作为参数传递感觉过大,那么它也不适合作为值接收器
  • 并发调用或方法可能修改接收器:若变更需在原始接收器中可见,必须使用指针
  • 元素是指向可变对象的指针:优先使用指针接收器以明确意图

When to Use Value Receiver

使用值接收器的场景

  • Small unchanging structs or basic types: Value receiver for efficiency
  • Map, func, or chan: Don't use a pointer to them
  • Slice without reslicing/reallocating: Don't use a pointer if the method doesn't reslice or reallocate the slice
  • Small value types with no mutable fields: Types like
    time.Time
    with no mutable fields and no pointers work well as value receivers
  • Simple basic types:
    int
    ,
    string
    , etc.
go
// Value receiver: small, immutable type
type Point struct {
    X, Y float64
}

func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

// Pointer receiver: method mutates receiver
func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}

// Pointer receiver: contains sync.Mutex
type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}
  • 小型不可变结构体或基础类型:值接收器更高效
  • Map、Func或Chan:不要使用它们的指针
  • 无需重新切片/重新分配的Slice:若方法不重新切片或重新分配切片,无需使用指针
  • 无可变字段的小型值类型:如
    time.Time
    这类无可变字段且无指针的类型,适合使用值接收器
  • 简单基础类型
    int
    string
go
// Value receiver: small, immutable type
type Point struct {
    X, Y float64
}

func (p Point) Distance(q Point) float64 {
    return math.Hypot(q.X-p.X, q.Y-p.Y)
}

// Pointer receiver: method mutates receiver
func (p *Point) ScaleBy(factor float64) {
    p.X *= factor
    p.Y *= factor
}

// Pointer receiver: contains sync.Mutex
type Counter struct {
    mu    sync.Mutex
    count int
}

func (c *Counter) Increment() {
    c.mu.Lock()
    c.count++
    c.mu.Unlock()
}

Consistency Rule

一致性规则

Don't mix receiver types. Choose either pointers or struct types for all available methods on a type. If any method needs a pointer receiver, use pointer receivers for all methods.
go
// Good: Consistent pointer receivers
type Buffer struct {
    data []byte
}

func (b *Buffer) Write(p []byte) (int, error) { /* ... */ }
func (b *Buffer) Read(p []byte) (int, error)  { /* ... */ }
func (b *Buffer) Len() int                     { return len(b.data) }

// Bad: Mixed receiver types
func (b Buffer) Len() int                      { return len(b.data) }  // inconsistent

不要混合使用接收器类型。为一个类型的所有方法选择指针或结构体类型。如果任何方法需要指针接收器,所有方法都应使用指针接收器。
go
// Good: Consistent pointer receivers
type Buffer struct {
    data []byte
}

func (b *Buffer) Write(p []byte) (int, error) { /* ... */ }
func (b *Buffer) Read(p []byte) (int, error)  { /* ... */ }
func (b *Buffer) Len() int                     { return len(b.data) }

// Bad: Mixed receiver types
func (b Buffer) Len() int                      { return len(b.data) }  // inconsistent

Quick Reference

速查参考

ConceptPatternNotes
Implicit implementationJust implement the methodsNo
implements
keyword
Type assertion
v := x.(Type)
Panics if wrong type
Safe type assertion
v, ok := x.(Type)
Returns zero value + false
Type switch
switch v := x.(type)
Variable has correct type per case
Interface embedding
type RW interface { Reader; Writer }
Union of methods
Struct embedding
type S struct { *T }
Promotes T's methods
Access embedded field
s.T
or
s.T.Method()
Type name is field name
Interface check
var _ I = (*T)(nil)
Compile-time verification
GeneralityReturn interface from constructorHide implementation

概念模式说明
隐式实现只需实现对应方法无需
implements
关键字
类型断言
v := x.(Type)
类型错误会导致恐慌
安全类型断言
v, ok := x.(Type)
返回零值+false
类型切换
switch v := x.(type)
变量在每个case中为正确类型
接口嵌入
type RW interface { Reader; Writer }
方法的并集
结构体嵌入
type S struct { *T }
提升T的方法
访问嵌入字段
s.T
s.T.Method()
类型名即为字段名
接口检查
var _ I = (*T)(nil)
编译时验证
通用性从构造函数返回接口隐藏实现细节

See Also

扩展阅读

  • go-style-core: Core Go style principles and formatting
  • go-naming: Interface naming conventions (Reader, Writer, etc.)
  • go-error-handling: Error interface and custom error types
  • go-functional-options: Using interfaces for flexible APIs
  • go-defensive: Defensive programming patterns
  • go-style-core:Go核心风格原则与格式化
  • go-naming:接口命名规范(Reader、Writer等)
  • go-error-handling:错误接口与自定义错误类型
  • go-functional-options:使用接口实现灵活API
  • go-defensive:防御式编程模式