首页 > 其他分享 >Go语言精进之路读书笔记第24条——方法集合决定接口实现

Go语言精进之路读书笔记第24条——方法集合决定接口实现

时间:2024-02-13 14:11:41浏览次数:40  
标签:24 读书笔记 DumpMethodSet 接口类型 M1 func M2 Go type

24.1 方法集合

方法决定接口实现:
如果某个自定义类型T的方法集合是某个接口类型的方法集合的超集,那么就说类型T实现了该接口,并且类型T的变量可以赋值给该接口类型的变量

Go语言规范,对于非接口类型的自定义类型T:

  • 类型T,方法集合由所有receiver为T类型的方法组成
  • 类型*T,方法集合由所有receiver为T和*T类型的方法组成
type Interface interface {
    M1()
    M2()
}

type T struct{}

func (t T) M1()  {}
func (t *T) M2() {}

func main() {
    var t T
    var pt *T
    DumpMethodSet(&t) //M1
    DumpMethodSet(&pt) //M1 M2
    DumpMethodSet((*Interface)(nil)) //M1 M2
}

24.2 类型嵌入与方法集合

Go的设计哲学之一是偏好组合,具体的方式就是利用类型嵌入(type embedding)

1.在接口类型中嵌入接口类型

通过嵌入其他接口类型而创建的新接口类型的方法集合包含了被嵌入接口类型的方法集合

例子:基于已有接口类型构建新接口类型,io包的ReadWrite、ReadWriterCloser

func main() {
    DumpMethodSet((*io.Writer)(nil)) //Write
    DumpMethodSet((*io.Reader)(nil)) //Read
    DumpMethodSet((*io.Closer)(nil)) //Close
    DumpMethodSet((*io.ReadWriter)(nil)) //Read Write
    DumpMethodSet((*io.ReadWriteCloser)(nil)) //Read Write Close
}

不过在Go1.14之前的版本中,这种方式有一个约束,那就是被嵌入的接口类型的方法集合不能有交集,同时被嵌入的接口类型的方法集合中的方法不能与新接口中其他方法同名
自Go1.14版本之后,Go语言去除了这个约束

type Interface1 interface {
    M1()
}

type Interface2 interface {
    M1()
    M2()
}

type Interface3 interface {
    Interface1
    Interface2 //Go 1.14之前版本报错:duplicate method M1
}

type Interface4 interface {
    Interface2
    M2() //Go 1.14之前版本报错:duplicate method M2
}

func main() {
    DumpMethodSet((*Interface3)(nil)) //M1 M2
}

2.在结构体类型中嵌入接口类型

在结构体类型中嵌入接口类型后,结构体类型的方法集合包含被嵌入接口类型的方法集合

type Interface interface {
    M1()
    M2()
}

type T struct {
    Interface
}

func (T) M3() {}

func main() {
    DumpMethodSet((*Interface)(nil)) //M1 M2
    var t T
    var pt *T
    DumpMethodSet(&t) //M1 M2 M3
    DumpMethodSet(&pt) //M1 M2 M3
}

当结构体类型中嵌入多个接口类型且这些接口类型的方法集合存在交集时
(1) 优先选择结构体自身实现的方法
(2) 如果结构体自身并未实现,那么将查找结构体中的嵌入接口类型的方法集合中是否有该方法,如果有,则提升(promoted)为结构体的方法

type Interface interface {
    M1()
    M2()
}

type T struct {
    Interface
}

func (T) M1() {
    println("T's M1")
}

type S struct{}

func (S) M1() {
    println("S's M1")
}
func (S) M2() {
    println("S's M2")
}

func main() {
    var t = T{
        Interface: S{},
    }

    t.M1() //T's M1
    t.M2() //S's M2
}

(3) 如果结构体嵌入了多个接口类型且这些类型的方法集合存在交集,那么Go编译器将报错,除非结构体自己实现了交集中的所有方法

type Interface interface {
    M1()
    M2()
    M3()
}

type Interface1 interface {
    M1()
    M2()
    M4()
}

type T struct {
    Interface
    Interface1
}

// 如果T未实现M1和M2,编译器会给出错误提示:ambiguous selector t.M1/t.M2
func (T) M1() { println("T's M1") }
func (T) M2() { println("T's M2") }

func main() {
    t := T{}
    t.M1() //T's M1
    t.M2() //T's M2
}

例子:在单元测试中,通过在结构体中嵌入接口,来测试接口的某一个方法。利用的正是结构体类型在嵌入某接口类型的同时,也实现了该接口的特性。

type Result struct {
    Count int
}

func (r Result) Int() int { return r.Count }

type Rows []struct{}

type Stmt interface {
    Close() error
    NumInput() int
    Exec(stmt string, args ...string) (Result, error)
    Query(args []string) (Rows, error)
}

// 返回男性员工总数
func MaleCount(s Stmt) (int, error) {
    result, err := s.Exec("select count(*) from employee_tab where gender=?", "1")
    if err != nil {
        return 0, err
    }

    return result.Int(), nil
}

//单元测试,快速构建实现了Stmt接口的伪对象fakeStmtForMaleCount,仅需实现Exec方法即可
type fakeStmtForMaleCount struct {
    Stmt
}

func (fakeStmtForMaleCount) Exec(stmt string, args ...string) (Result, error) {
    return Result{Count: 5}, nil
}

func TestEmployeeMaleCount(t *testing.T) {
    f := fakeStmtForMaleCount{}
    c, _ := MaleCount(f)
    if c != 5 {
        t.Errorf("want: %d, actual: %d", 5, c)
        return
    }
}

3.在结构体类型中嵌入结构体类型

外部的结构体类型T可以“继承”嵌入的结构体类型的所有方法的实现,并且无论是T类型的变量实例还是*T类型的变量实例,都可以调用所有“继承”的方法

但是T和*T的方法集合是有差别的:

  • T = T1 + *T2
  • T = *T1 + *T2

疑问:Go语言中两个类型的方法集合不一样有什么影响?

type T1 struct{}

func (T1) T1M1()   { println("T1's M1") }
func (T1) T1M2()   { println("T1's M2") }
func (*T1) PT1M3() { println("PT1's M3") }

type T2 struct{}

func (T2) T2M1()   { println("T2's M1") }
func (T2) T2M2()   { println("T2's M2") }
func (*T2) PT2M3() { println("PT2's M3") }

type T struct {
    T1
    *T2
}

func main() {
    t := T{
        T1: T1{},
        T2: &T2{},
    }

    //无论T类型变量还是*T类型变量实例都可以调用所有“继承”的方法
    println("call method through t:")
    t.T1M1()
    t.T1M2()
    t.PT1M3()
    t.T2M1()
    t.T2M2()
    t.PT2M3()

    println("\ncall method through pt:")
    pt := &t
    pt.T1M1()
    pt.T1M2()
    pt.PT1M3()
    pt.T2M1()
    pt.T2M2()
    pt.PT2M3()
    println("")

    var t1 T1
    var pt1 *T1
    DumpMethodSet(&t1)  //T1M1 T1M2
    DumpMethodSet(&pt1) //PT1M3 T1M1 T1M2

    var t2 T2
    var pt2 *T2
    DumpMethodSet(&t2)  //T2M1 T2M2
    DumpMethodSet(&pt2) //PT2M3 T2M1 T2M2

    DumpMethodSet(&t)  //PT2M3 T1M1 T1M2 T2M1 T2M2
    DumpMethodSet(&pt) //PT1M3 PT2M3 T1M1 T1M2 T2M1 T2M2
}

24.3 defined类型的方法集合

type MyInterface I
type Mystruct T

已有类型(I、T)被称为underlying类型,而新类型被称为defined类型

  • 基于接口创建的defined类型与原类型具有相同的方法集合,而基于自定义非接口类型创建的defined类型的方法集合为空
  • 因此对于基于自定义非接口类型的defined类型,即使原类型实现了某些接口,新defined类型仍需重新实现
type T struct{}

func (T) M1()  {}
func (*T) M2() {}

type Interface interface {
    M1()
    M2()
}

type T1 T
type Interface1 Interface

func main() {
    var t T
    var pt *T
    var t1 T1
    var pt1 *T1

    DumpMethodSet(&t)  //M1
    DumpMethodSet(&t1) //empty

    DumpMethodSet(&pt)  //M1 M2
    DumpMethodSet(&pt1) //empty

    DumpMethodSet((*Interface)(nil))  //M1 M2
    DumpMethodSet((*Interface1)(nil)) //M1 M2
}

24.4 类型别名的方法集合

// $GOROOT/src/builtin/builtin.go
type byte = uint8
type rune = int32

类型别名与原类型拥有完全相同的方法集合,无论原类型是接口类型还是非接口类型

type T struct{}

func (T) M1()  {}
func (*T) M2() {}

type Interface interface {
    M1()
    M2()
}

type T1 = T
type Interface1 = Interface

func main() {
    var t T
    var pt *T
    var t1 T1
    var pt1 *T1

    DumpMethodSet(&t)  //M1
    DumpMethodSet(&t1) //M1

    DumpMethodSet(&pt)  //M1 M2
    DumpMethodSet(&pt1) //M1 M2

    DumpMethodSet((*Interface)(nil))  //M1 M2
    DumpMethodSet((*Interface1)(nil)) //M1 M2
}

标签:24,读书笔记,DumpMethodSet,接口类型,M1,func,M2,Go,type
From: https://www.cnblogs.com/brynchen/p/18014579

相关文章

  • 2024年初找新SAP项目的几个体会
    2024年初找新SAP项目的几个体会 1.一定要找业界知名大型乙方实施公司的项目。比如IBM,AC,凯捷或者印度公司比如TCS,InfoSys等业界知名外企乙方公司的项目,都是相对靠谱的。这些大公司能够拿下业界土豪客户比如跨国外企的项目,因为他们牌子大业界口碑好,能得到不差钱大客户的亲赖和......
  • Codeforces Round 924 (Div. 2)
    B.Equalize与数组的原始顺序无关,直接排序,然后用双指针滑动范围a[r]-a[l]小于n#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;voidsolve(){ intn; cin>>n; set<int>st; for(inti=1;i<=n;i++){ intx; cin>>x; st.insert(x); } ......
  • Go语言精进之路读书笔记第23条——理解方法的本质以选择正确的receiver类型
    和函数相比,Go语言中的方法在声明形式上仅仅多了一个参数,Go称之为receiver参数。receiver参数是方法与类型之间的纽带。Go方法特点:方法名的首字母是否大写决定了该方法是不是导出方法。方法定义要与类型定义放在同一个包内。由此可以推出,不能为原生类型(如int/float64/map等)添加......
  • C1. Good Subarrays (Easy Version)
    找子数组的个数双指针#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;constintN=2e5+10;inta[N];voidsolve(){ intn; cin>>n; for(inti=1;i<=n;i++)cin>>a[i]; intl=1,r=1; intans=0; while(l<=r){ if(l>n||r>......
  • Go语言精进之路读书笔记第22条——使用defer让函数更简介、更健壮
    22.1defer的运行机制在Go中,只有在函数和方法内部才能使用defer。defer关键字后面只能接函数或方法,这些函数被成为deferred函数。defer将它们注册到其所在goroutine用于存放deferred函数的栈数据结构中。在执行defer的函数退出前,按后进先出(LIFO)的顺序调度执行。22.2defer的......
  • Google Earth Pro谷歌地球专业版
    GoogleEarthPro谷歌地球专业版,标准版,在国内可以用的,常见的黑屏问题可以解决的需要解决黑屏问题的可以找我(V×:F2233F) ......
  • Go语言精进之路读书笔记第21条——让自己习惯于函数是"一等公民"
    21.1什么是"一等公民"(1)正常创建//$GOROOT/src/fmt/print.gofuncnewPrinter()*pp{p:=ppFree.Get().(*pp)p.panicking=falsep.erroring=falsep.wrapErrs=falsep.fmt.init(&p.buf)returnp}(2)在函数内创建,定义匿名函数赋值给......
  • Go语言精进之路读书笔记第20条——在init函数中检查包级变量的初始状态
    20.1认识init函数init函数的特点:运行时调用,Go程序中不能显式调用顺序执行,等待一个init函数执行完毕并返回后再执行下一个init函数在整个Go程序生命周期内仅会被执行一次先被传递给Go编译器的源文件中的init函数先被执行,同一个源文件中的多个init函数按声明顺序依次执行。但......
  • 2024/2/12学习进度笔记
    sparkrdd持久化frompysparkimportSparkContext,SparkConfimportosimportrefrompyspark.storagelevelimportStorageLevelos.environ['SPARK_HOME']='/export/server/spark'PYSPARK_PYTHON="/root/anaconda3/envs/pyspark_env/bin......
  • WC 2024 游记
    WC2024游记Day-2~Day-1在酒店颓废。Day0不存在的一天。Day1报到日。上午从酒店搬了出来(行李箱好重qvq)。坐上了cq神奇的地铁(轻轨),看着地铁上天下地的感觉还是很奇妙的。大抵是中午的时候到了育才。离育才最近的地铁口可以坐环线(转圈圈!),cq地铁真神奇。到了门口发现每......