go-interfaces
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGo 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
Interface Basics
接口基础
Interfaces in Go specify behavior: if something can do this, it can be used
here. Types implement interfaces implicitly—no keyword needed.
implementsgo
// 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中的接口用于定义行为:只要某个类型能完成这件事,它就能在此处被使用。类型会隐式实现接口——无需关键字。
implementsgo
// 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 suffix:
, , , .
-erReaderWriterFormatterStringer按照惯例,单方法接口采用“方法名+-er”后缀命名:、、、。
ReaderWriterFormatterStringerType Assertions
类型断言
A type assertion extracts the concrete value from an interface.
类型断言用于从接口中提取具体值。
Basic Syntax
基础语法
go
value.(typeName)The result has the static type . The type must be either:
typeName- 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.Writergo
value.(typeName)结果的静态类型为。该类型必须是:
typeName- 接口当前持有的具体类型,或者
- 该值可转换为的另一个接口类型
go
var w io.Writer = os.Stdout
f := w.(*os.File) // Extract *os.File from io.WriterComma-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, is the zero value (empty string) and is
false.
strok若不做检查,失败的断言会导致运行时恐慌。使用逗号-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")
}如果断言失败,会是零值(空字符串),为false。
strokChecking 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
syntax:
.(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
}类型切换通过语法来发现接口变量的动态类型:
.(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.Hash32go
// Good: Constructor returns interface type
func NewHash() hash.Hash32 {
return &myHash{} // unexported type
}
// The implementation is hidden; callers only see hash.Hash32Real-World Example: crypto/cipher
实际案例:crypto/cipher
The package demonstrates this pattern:
crypto/ciphergo
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) StreamBenefits:
- 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/ciphergo
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 can do what a does and what a does—it's a
union of the embedded interfaces.
ReadWriterReaderWriterRule: 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
}ReadWriterReaderWriter规则:只有接口可嵌入到接口中。
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. satisfies
, , and without explicit forwarding.
bufio.ReadWriterio.Readerio.Writerio.ReadWritergo
// 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.ReadWriterio.Readerio.Writerio.ReadWriterEmbedding 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 ReadWriterName Conflict Resolution
名称冲突解决
- Outer fields/methods hide inner ones: A field on the outer type hides any
Xin embedded typesX - Same level conflicts are errors: Embedding two types with the same method name at the same level causes an error (unless never accessed)
- 外部字段/方法覆盖内部字段/方法:外部类型的字段会覆盖嵌入类型中的所有
XX - 同层级冲突会报错:嵌入两个在同一层级有同名方法的类型会导致错误(除非从未访问该方法)
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 doesn't implement .
*RawMessagejson.Marshaler使用空白标识符赋值来验证类型是否实现了接口:
go
// Verify *RawMessage implements json.Marshaler at compile time
var _ json.Marshaler = (*RawMessage)(nil)如果未实现,会导致编译错误。
*RawMessagejson.MarshalerWhen 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 with no mutable fields and no pointers work well as value receivers
time.Time - Simple basic types: ,
int, etc.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()
}- 小型不可变结构体或基础类型:值接收器更高效
- 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) } // inconsistentQuick Reference
速查参考
| Concept | Pattern | Notes |
|---|---|---|
| Implicit implementation | Just implement the methods | No |
| Type assertion | | Panics if wrong type |
| Safe type assertion | | Returns zero value + false |
| Type switch | | Variable has correct type per case |
| Interface embedding | | Union of methods |
| Struct embedding | | Promotes T's methods |
| Access embedded field | | Type name is field name |
| Interface check | | Compile-time verification |
| Generality | Return interface from constructor | Hide implementation |
| 概念 | 模式 | 说明 |
|---|---|---|
| 隐式实现 | 只需实现对应方法 | 无需 |
| 类型断言 | | 类型错误会导致恐慌 |
| 安全类型断言 | | 返回零值+false |
| 类型切换 | | 变量在每个case中为正确类型 |
| 接口嵌入 | | 方法的并集 |
| 结构体嵌入 | | 提升T的方法 |
| 访问嵌入字段 | | 类型名即为字段名 |
| 接口检查 | | 编译时验证 |
| 通用性 | 从构造函数返回接口 | 隐藏实现细节 |
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:防御式编程模式