Golang中混合使用类型方法接收器的探讨
Golang中混合使用类型方法接收器的探讨
在一个类型上使用混合接收器是否是可接受/有效的做法?如下所示,bind 是指针接收器,因为它会修改类型;而 validate 是值接收器,因为它不修改类型。虽然这可以正常工作,但这是否符合 Go 语言的惯用法?
谢谢
type Request struct {
Name string
Price int
}
func (r *Request) bind(name string) error {
r.Name = name
}
func (r Request) validate() error {
if r.Name == "yow" {
return some error
}
return nil
}
更多关于Golang中混合使用类型方法接收器的探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
有道理。我之前没注意到接口这个点。感谢你的详细解释。
更多关于Golang中混合使用类型方法接收器的探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
不客气。
除了上述理由之外,这里还有一篇文章强烈建议默认对所有方法使用指针接收器:
方法应该声明在 T 还是 *T 上 | Dave Cheney
Dave Cheney 提出的观点是,使用非指针接收器的方法可能会无意中复制一个本不应被复制的值,例如包含互斥锁的结构体。
嗨,
这是可行的,而且我不认为这违反任何惯用法。
然而,这里有一个需要注意的地方。如果你添加一个像这样的接口:
type Requester interface {
bind(string) error
validate() error
}
以及一个函数
func iWantARequester(r Requester) {}
那么你就不能将 Request 类型的变量传递给这个函数。你会得到一个错误提示:
“Request 没有实现 Requester(bind 方法具有指针接收器)”
为什么?因为方法 bind() 不能在 Request 类型上调用。你需要一个指针类型,即 &Request,这样 bind() 才能通过指针访问接收器。
非指针类型 Request 只“拥有”两个方法中的一个,即 validate()。
而指针类型 &Request 则拥有两个方法,因为它既能跟随 bind() 中的指针接收器,也能访问 validate() 中的非指针接收器。
我不确定这个解释是否足够清楚。这里是 Go Playground 上的一个可运行代码,展示了其中的区别。运行这段代码,你会在 iWantARequester(r) 处得到一个错误。注释掉 r 周围的代码,你会发现 pr 可以正常工作。
另外,这里是指向方法集规范的链接,它这样解释这种情况:
(强调是我的)
在你的代码中,T 就是 Request,而“指向已定义类型 T 的指针”就是 &Request。
在 Go 语言中,混合使用值接收器和指针接收器是完全有效且符合惯用法的做法。你的示例代码展示了典型的应用场景:指针接收器用于修改接收者状态,值接收器用于只读操作。这种做法在标准库和实际项目中都很常见。
以下是一个更完整的示例,展示了这种混合使用的典型模式:
package main
import (
"errors"
"fmt"
)
type Request struct {
Name string
Price int
}
// 指针接收器 - 修改结构体状态
func (r *Request) Bind(name string, price int) {
r.Name = name
r.Price = price
}
// 值接收器 - 只读验证,不修改状态
func (r Request) Validate() error {
if r.Name == "" {
return errors.New("name cannot be empty")
}
if r.Price <= 0 {
return errors.New("price must be positive")
}
return nil
}
// 另一个值接收器 - 计算派生值
func (r Request) Display() string {
return fmt.Sprintf("Request: %s ($%d)", r.Name, r.Price)
}
func main() {
req := &Request{}
// 使用指针接收器修改状态
req.Bind("Product", 100)
// 使用值接收器进行验证
if err := req.Validate(); err != nil {
fmt.Println("Validation error:", err)
}
// 使用值接收器获取显示信息
fmt.Println(req.Display())
// 也可以在值类型上调用指针接收器方法(Go会自动处理)
req2 := Request{}
req2.Bind("Another", 200)
fmt.Println(req2.Display())
}
关键点:
- 一致性原则:对于需要修改接收者的方法使用指针接收器,对于不需要修改的方法使用值接收器
- 自动转换:Go 编译器会自动在值和指针之间转换,所以
req.Validate()和(&req).Validate()都能工作 - 性能考虑:对于大型结构体,即使方法不修改状态,使用指针接收器可以避免复制开销
- 接口实现:指针接收器方法只能通过指针类型实现接口,值接收器方法则值和指针类型都能实现
标准库中的 time.Time 类型就是一个很好的例子:
- 所有方法都是值接收器,因为
time.Time是不可变类型 - 任何修改操作都返回新的实例
你的代码模式完全符合 Go 的惯用法,这种根据方法功能选择接收器类型的做法是推荐的最佳实践。

