Golang中接口的命名规范探讨
Golang中接口的命名规范探讨 我想创建一个栈接口,然后实现它的两种方式:StackImpl 和 StackConcurrentImpl(第二种实现是并发安全的)。
关于命名有什么建议吗?我应该创建两个包,stack 和 concurrentStack,然后在每个包中定义一个 Stack 接口,并有一个实现该接口的 Impl 结构体吗?
命名接口及其实现结构体的惯用方式是什么?
Impl 后缀绝对是 Java 风格的,在 Go 语言中并不常见。
只需定义一个栈接口,然后在普通的栈和并发栈上实现其方法即可。
更多关于Golang中接口的命名规范探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
如果一个类型的主要作用是以一种特定方式实现接口,并且这种特定方式对用户很重要,那么就将这种方式与接口名称一起包含在类型名称中。在 Java 中,接口
Map由HashMap和TreeMap实现。Hash和Tree是如何实现,Map是是什么。
大家的回答都很棒。你关于“是什么”和“如何实现”的观点非常好——我会坚持这一点。
次要说明:我个人对名称中的缩写非常反感,除了众所周知的缩写如JSON、XML、HTML等。如今大多数(所有)IDE都会提供帮助,所以你无需完整拼写整个单词,因此我会避免使用像 StackImpl 和 StackConcurrentImpl 这样的名称,而是完整写出全名。我更喜欢清晰、描述性的名称,而不是简短的名字。
(顺便说一句,这是我不喜欢Go的一点。在我看来,标准库中充满了糟糕的缩写,比如strconv、regexp等等。)
不过,如果这对你有效的话……
官方的 Effective Go 文档中有一个关于接口命名的章节:
按照惯例,单方法接口的名称由方法名加上 -er 后缀或类似的修改构成,以形成一个代理名词:
Reader、Writer、Formatter、CloseNotifier等。
通常,保持接口中方法的数量尽可能少是一种良好的实践(即“SOLID”原则中的“I”)。一个方法是一个很好的数量,这将允许你遵循上述命名约定。
如果你无法将接口中的方法数量减少到一个,那么接口的名称至少应该告诉读者,实现该接口的对象可以期待什么样的行为。Stack 可能是一个好名字,因为对普通读者来说,它暗示了像 Push 和 Pop 这样的行为。
如果一个类型只是实现了一个接口,但这并不是它的主要功能,那么它的名称就不应该引用接口的名称。将 Reader 包含在每个实现 Reader 接口的类型的名称中,这是一个糟糕的想法。许多类型都没有在其名称中包含 Reader。
如果一个类型的主要功能是以一种特定的方式实现一个接口,并且这种特定方式对用户来说很重要,那么就将这种方式与接口名称一起包含在类型的名称中。在 Java 中,接口 Map 由 HashMap 和 TreeMap 实现。Hash 和 Tree 是如何实现,Map 是什么。
永远不要使用 Impl,或者更糟糕的 SimpleStack、BasicStack 或其他无法说明类型如何实现接口的名称。如果你无法想出一个能告诉读者实现方式的好名字,也许根本就不需要接口。
在Go语言中,接口和实现的命名遵循一些惯用约定。对于你的场景,通常建议将接口和实现放在同一个包中,并使用清晰的命名来区分它们。
接口命名
- 接口名通常使用名词,如果接口只有一个方法,通常以"er"结尾(如
Reader、Writer) - 对于栈这种抽象数据类型,直接使用
Stack作为接口名是合适的
实现命名
- 实现结构体通常使用描述性的名称,表明其特性
- 避免使用
Impl后缀,而是使用更具描述性的名称
推荐方案
// stack/stack.go
package stack
// Stack 接口定义
type Stack interface {
Push(item interface{})
Pop() interface{}
Peek() interface{}
IsEmpty() bool
Size() int
}
// ArrayStack 基于数组的实现
type ArrayStack struct {
items []interface{}
}
func NewArrayStack() *ArrayStack {
return &ArrayStack{
items: make([]interface{}, 0),
}
}
func (s *ArrayStack) Push(item interface{}) {
s.items = append(s.items, item)
}
func (s *ArrayStack) Pop() interface{} {
if s.IsEmpty() {
return nil
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item
}
// ... 其他方法实现
// ConcurrentStack 并发安全的栈实现
type ConcurrentStack struct {
items []interface{}
mu sync.RWMutex
}
func NewConcurrentStack() *ConcurrentStack {
return &ConcurrentStack{
items: make([]interface{}, 0),
}
}
func (s *ConcurrentStack) Push(item interface{}) {
s.mu.Lock()
defer s.mu.Unlock()
s.items = append(s.items, item)
}
func (s *ConcurrentStack) Pop() interface{} {
s.mu.Lock()
defer s.mu.Unlock()
if len(s.items) == 0 {
return nil
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item
}
// ... 其他方法实现
使用示例
package main
import (
"fmt"
"yourmodule/stack"
)
func main() {
// 使用普通栈
arrayStack := stack.NewArrayStack()
arrayStack.Push(1)
arrayStack.Push(2)
fmt.Println(arrayStack.Pop()) // 2
// 使用并发安全栈
concurrentStack := stack.NewConcurrentStack()
concurrentStack.Push("a")
concurrentStack.Push("b")
fmt.Println(concurrentStack.Pop()) // "b"
}
关键点
- 单一包结构:将相关接口和实现在同一个包中,便于管理
- 描述性命名:
ArrayStack和ConcurrentStack清晰地表明了实现特性 - 构造函数模式:使用
NewXxx()函数创建实例,这是Go的惯用方式 - 接口在前:在包中先定义接口,再定义实现
这种设计允许用户通过接口类型使用栈,同时可以根据需要选择具体的实现:
func processStack(s stack.Stack) {
// 可以接受任何Stack实现
s.Push("data")
// ...
}
不需要创建单独的concurrentStack包,因为并发安全是实现的特性,不是接口的职责。用户可以根据需要选择使用ArrayStack或ConcurrentStack。

