Go 语言极速入门5 - 面向接口

一、接口

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)
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,937评论 0 38
  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi阅读 7,458评论 0 10
  • 01 前段时间网上热议的财务自由9个阶段,对照了一眼,还是决定默默地搬砖去: 第一阶段——菜场自由 在菜场,自己愿...
    明月渠阅读 509评论 0 0
  • 最近有LSPL职业选手在接受采访的时候说过一句话,在我们LPL那是一颗石子,激起千层浪花啊! 他说了什么? 他说,...
    黄铜刀阅读 666评论 1 1
  • 满腔热血把师学会,当了教师吃苦受罪。 急难险重必需到位,教师育人终日疲惫。 学生告状回回都对,工资不高还要交税。 ...
    钝角阅读 639评论 0 39