V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
zzyphp111
V2EX  ›  问与答

请教一个 golang 接口方法设计的问题

  •  
  •   zzyphp111 · 2021-08-14 21:25:32 +08:00 · 1769 次点击
    这是一个创建于 957 天前的主题,其中的信息可能已经有所发展或是发生改变。

    背景 :通过断言是怎么实现接口实例化注入

    疑问的关键代码:

    var _ log.Logger = (*stdLogger)(nil)
    

    提出疑问:

    首先有这样一个结构,表示 log 结构器

    
    // Logger is a logger interface.
    type Logger interface {
    	Log(level Level, keyvals ...interface{}) error
    }
    
    

    接下来按照正规流程,就是利用这个 logger 器,做些初始化 log 的方法

    
    var _ Logger = (*stdLogger)(nil)
    
    type stdLogger struct {
    	log  *log.Logger
    	pool *sync.Pool
    }
    
    // NewStdLogger new a logger with writer.
    func NewStdLogger(w io.Writer) Logger {
    	return &stdLogger{
    		log: log.New(w, "", 0),
    		pool: &sync.Pool{
    			New: func() interface{} {
    				return new(bytes.Buffer)
    			},
    		},
    	}
    }
    
    

    问题就在这里为什么,明明这个 new 方法返回的是 stdLogger 这个结构,而为啥返回类型是 Logger,我发现个特别点就是第一行做了断言处理,这个是什么原理,一般断言也是把已知结构转成明确固定结构,为啥这里明明 stdLogger 结构体和 Logger 结构体完全不一样,就可以实现断言?

    望大家不吝赐教,多多指出,或相关代码实现,本人对接口实现方面比较小白。

    15 条回复    2022-11-16 17:26:35 +08:00
    johnwood
        1
    johnwood  
       2021-08-14 21:34:52 +08:00
    golang 的类型系统和 java 之类的不一样,他是鸭子类型系统,结构不用明确表明自己实现了某个接口,也没有 java 那样的“继承”。go 的倾向是组合优于继承。

    回到问题,估计因为 stdLogger 里有嵌入的 log *log.Logger
    zzyphp111
        2
    zzyphp111  
    OP
       2021-08-14 21:40:22 +08:00
    zzyphp111
        3
    zzyphp111  
    OP
       2021-08-14 22:42:33 +08:00
    @johnwood #1 这也是我所以疑问的,你看 zap 的引入就没有嵌入 log *log.Logger,但也断言成功了,这是为什么 https://github.com/go-kratos/kratos/blob/main/examples/log/zap.go
    gjquoiai
        4
    gjquoiai  
       2021-08-14 22:57:21 +08:00
    一个编译时检查某个东西是否实现了某个接口的技巧,现在用的不多了,都用 linter 了
    johnwood
        5
    johnwood  
       2021-08-14 23:23:56 +08:00
    @zzyphp111 你看#3 连接的 16 行 有嵌入自己的实现的 logger
    comwrg
        6
    comwrg  
       2021-08-14 23:25:06 +08:00
    4L 说的对,是用来编译期保证正确的实现了某个接口,而不是等着运行之后程序 panic
    EscYezi
        7
    EscYezi  
       2021-08-15 00:05:03 +08:00 via iPhone
    只要实现了 Logger 所有的方法就实现了 Logger 接口
    楼主提到的 stdLogger 和 zapLogger 可以断言成功是因为两个结构体都实现了 Logger 接口的 Log 方法,和内嵌的那个 log 成员没关系
    johnwood
        8
    johnwood  
       2021-08-15 00:12:45 +08:00
    @EscYezi 你看 NewStdLogger 实例化的是什么
    EscYezi
        9
    EscYezi  
       2021-08-15 00:18:07 +08:00 via iPhone
    @johnwood #8 实例化的 stdLogger 啊,stdLogger 实现了 Logger 接口
    EscYezi
        10
    EscYezi  
       2021-08-15 00:52:54 +08:00
    可以尝试运行一下这段代码


    package main

    import "fmt"

    type Logger interface {
    Log(...interface{})
    }
    type myLogger struct {
    }

    func (l *myLogger) Log(...interface{}) {
    fmt.Println("my logger")
    }
    func main() {
    var l Logger = (*myLogger)(nil)
    l.Log("aaa")
    }
    johnwood
        11
    johnwood  
       2021-08-15 10:44:37 +08:00
    @EscYezi 看了 2 楼的连接 你说的对 stdLogger 实现了 Logger 接口
    zzyphp111
        12
    zzyphp111  
    OP
       2021-08-15 12:14:50 +08:00
    @EscYezi #10 嗯嗯,看了你的,感觉非常直观,也解决了我的疑问, 只要存在这个结构体的实现 就可以断言成功。

    感谢
    kksco
        13
    kksco  
       2021-08-16 13:53:49 +08:00   ❤️ 1
    zzyphp111
        14
    zzyphp111  
    OP
       2021-08-16 14:26:42 +08:00
    @kksco #13 非常感谢
    Ansen
        15
    Ansen  
       2022-11-16 17:26:35 +08:00
    我也在这里迷糊了,搜索到这帖子了

    然后学习群的群友推荐了这篇文章,看完茅塞顿开

    https://go-kratos.dev/en/blog/
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1904 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 16:25 · PVG 00:25 · LAX 09:25 · JFK 12:25
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.