Go 语言学习5 - 面向接口

原水寒 · · 784 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一、接口

1.1、定义接口

// notifier 是一个定义了通知类行为的接口
type notifier interface {
    // 接口方法
    notify()
}

1.2、实现接口

使用值接收者实现接口

// notify 是使用值接收者实现 notifier interface 接口的方法
// sendNotification(&u) 和 sendNotification(u) 都可
func (u user) notify() {
    fmt.Println("notify", u)
}
  • 对于值接收者,sendNotification(&u) 和 sendNotification(u) 都可

使用指针接收者实现接口

// notify 是使用指针接收者实现 notifier interface 接口的方法
// 只能使用 sendNotification(&u)
func (u *user) notify() {
    fmt.Println("notify", *u)
}
  • 对于指针接收者,只能使用 sendNotification(&u)

非常重要的点 - 值接收者与指针接收的对比

type User struct {
    Name  string
    Email string
}
// 注意这个是值接收者
func (u User) Notify() error {
    u.Name = "alimon"
    return nil
}

func main() {
    u := &User{"Damon", "damon@xxoo.com"}
    u.Notify()
    log.Println(u.Name)
}

注意

  • 值接收者(func (u User) Notify() error)操作的是 User 的副本(不管调用者使用值还是地址),所以不会改变其内部的值,这里输出还是 Damon
  • 指针接收者(func (u *User) Notify() error)操作的是 User 本身,所以其内部的值会发生变化,这里输出是 alimon

什么时候使用值接收者,什么时候使用指针接收者

https://stackoverflow.com/questions/27775376/value-receiver-vs-pointer-receiver-in-golang 防止文章被删掉,下边直接摘抄出来:

  • If the receiver is a map, func or chan, don't use a pointer to it.
  • If the receiver is a slice and the method doesn't reslice or reallocate the slice, don't use a pointer to it.
  • If the method needs to mutate(使 receiver 改变) the receiver, the receiver must be a pointer.
  • If the receiver is a struct that contains a sync.Mutex or similar synchronizing field, the receiver must be a pointer to avoid copying.
  • If the receiver is a large struct or array, a pointer receiver is more efficient. How large is large? Assume it's equivalent to passing all its elements as arguments to the method. If that feels too large, it's also too large for the receiver.
  • Can function or methods, either concurrently or when called from this method, be mutating the receiver? A value type creates a copy of the receiver when the method is invoked, so outside updates will not be applied to this receiver. If changes must be visible in the original receiver, the receiver must be a pointer.
  • If the receiver is a struct, array or slice and any of its elements is a pointer to something that might be mutating, prefer a pointer receiver, as it will make the intention more clear to the reader.
  • If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used
  • If the receiver is a small array or struct that is naturally a value type (for instance, something like the time.Time type), with no mutable fields and no pointers, or is just a simple basic type such as int or string, a value receiver makes sense.
    A value receiver can reduce the amount of garbage that can be generated; if a value is passed to a value method, an on-stack copy can be used instead of allocating on the heap. (The compiler tries to be smart about avoiding this allocation, but it can't always succeed.) Don't choose a value receiver type for this reason without profiling first.
  • Finally, when in doubt, use a pointer receiver.

总结一下:

  • 如果需要改变 receiver 内部的属性值,选择指针接收者;
  • 如果 struct 中的一个方法使用了指针接收者,那么该 struct 内的全部方法都是用指针接收者 - 一致性

1.3、使用接口

// sendNotification 接受一个实现了 notifier 接口的值并发送通知
func sendNotification(n notifier) {
    n.notify()
}

func main() {
    u := user{"nana"}
    sendNotification(&u) // notify {nana}
}

二、实现多态

image.png

api.go

package api

// 定义接口
type Notifier interface {
    Notify()
}

接口名和接口方法大写表示 public,小写表示 java 的 default(即包隔离级别)

user.go

package impl

import "fmt"

// 定义实现类 user
type User struct {
    Name string
}

func (u *User) Notify() {
    fmt.Println("user", *u)
}

admin.go

package impl

import "fmt"

// 定义实现类 Admin,首字母大写表示 public
type Admin struct {
    Name string
}

func (a *Admin) Notify() {
    fmt.Println("admin", *a)
}

main.go

package main

import (
    // 相对于 GOPATH/src 下的地址
    "github.com/zhaojigang/helloworld/ooi/api"
    "github.com/zhaojigang/helloworld/ooi/impl"
)

func sendNotification(n api.Notifier) {
    n.Notify()
}

func main() {
    u := impl.User{"nana"}
    sendNotification(&u) // user {nana}

    a := impl.Admin{"zhao"}
    sendNotification(&a) // admin {zhao}
}

三、嵌入类型内部接口实现提升

基于上述程序修改 admin.go 和 main.go。

admin.go

package impl

// 定义实现类 Admin,首字母大写表示 public
type Admin struct {
    User // 嵌入类型
    Name string
}

注意:该类没有实现接口 notifier 的 notify() 方法,但是由于该类的内嵌类 User 实现了,内嵌类会提升到外部类中,所以 Admin 类也是 notifier 接口的实现类,其实现函数就是 User#Notify() ,当然,可以 Admin 可以自己实现 Notify() 来覆盖 User#Notify()。

main.go

package main

import (
    // 相对于 GOPATH/src 下的地址
    "github.com/zhaojigang/helloworld/ooi/api"
    "github.com/zhaojigang/helloworld/ooi/impl"
)

func sendNotification(n api.Notifier) {
    n.Notify()
}

func main() {
    u := impl.User{"nana"}
    sendNotification(&u) // user {nana}

    a := impl.Admin{u,"zhao"}
    sendNotification(&a) // user {nana}
}

四、组合接口

=========================== Api1 ===========================
package api

type Api1 interface {
    Api1()
}
=========================== Api2 ===========================
package api

type Api2 interface {
    Api2()
} 
=========================== Api12(组合接口) ===========================
package api

type Api12 interface {
    Api1
    Api2
}
=========================== Api12Impl ===========================
package impl

import "fmt"

type Api12Impl struct {
    Name string
}

func (api12 *Api12Impl) Api1()  {
    fmt.Println("api1", api12.Name)
}

func (api12 *Api12Impl) Api2()  {
    fmt.Println("api2", api12.Name)
}
=========================== main ===========================
package main

import (
    // 相对于 GOPATH/src 下的地址
    "github.com/zhaojigang/helloworld/ooi/api"
    "github.com/zhaojigang/helloworld/ooi/impl"
)

func sendNotification(n api.Api12) {
    n.Api1() // api1 nana
    n.Api2() // api2 nana
}

func main() {
    api12 := impl.Api12Impl{"nana"}
    sendNotification(&api12)
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:原水寒

查看原文:Go 语言学习5 - 面向接口

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

784 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传