Go 笔记二 多态的实现 struct & interface

前言

承接上文笔记一,继续细化下Go的其他语法细节。

结构体

某次编码经历:

// A 是一个复合结构体,其中包含了B,如下操作
req := a.B
req.B.attr = someValue
doSomething(&a) // 结果发现a.B.attr 并非 someValue

上述示例背后的逻辑?

基础定义

如果是有过Go Coding经历的基本都能理解结构体在Go编程中的效能,基本可以等同于类去理解,但是与传统类继承关系不一样的是,Go语言的结构体更像是以组合的形式进行构建。那么在组合结构体时,必须谨慎处理命名冲突。如下:

type A struct {
	attr int
}
type B struct {
	A
	attr int "this is b attr"
}

对于以上定义,如果调用:

b := new(B)
fmt.Println(b.attr)

则访问的是B 定义中的attr,并非匿名嵌套的A中的 attr 但是可以访问,顺序是外层定义的同名属性会覆盖内层的,对于上述定义形式实例b的属性中,会有两个attr一个本身外层定义,一个归于内层的匿名结构体A中的attr,可以通过 b.A.attr 访问内层。

// 修改前面定义
type C struct {
	attr int
}
type B struct {
	A
	C
}

此时,A,C组合匿名定义在B中,且同时具有attr属性,此种方式编译可以顺利进行,并且运行也无障碍。但是一旦出现 调用 b.attr 程序就会出错,因为编译器也不知道调用的属性具体是哪个中的,但是同理可以 b.A.attrb.C.attr 进行调用。

类型定义

用户可以自定义结构体,新定义的结构体就是一种新的类型。除了此种方式外,用户可以type关键字定义类型:

type Intger int  // 已有类型别名,特别是对于内置类型,不能够扩展方法,可以用别名新定义一个类型后,为别名类型扩展新方法
type func(int) int // 函数类型,所有具有想用的调用参数和返回参数的函数,都可以算作此函数类型

除过类型定义以外,经常会有一种区分所谓的 值类型引用类型。这二者的核型区别在于:

b := a  // 将a赋值给b
b.modify() // 对b做修改,a是否会受影响?

Go语言中,所有的赋值操作全部是 值拷贝,如果本来一个对象就是一个指针,那么拷贝过后对指针所指向的内存空间中的value进行修改,自然是把指针指向对象的值进行了修改。查看Go语言中内置类型的定义不难发现,slice,map,channel,interface此四者的定义,均值内部存放着对象的指针,因此通常也将此四者这为Go语言中的引用类型。

方法

函数和方法的核心区别在于,方法必须要有对应的接收者。对于笔者从C++入门,不禁也会觉得,Go的方法定义会不会也像C++那种存在重载多态
C++中对重载的定义式,一个类型的方法定义中,可以存在多个同名,但是调用参数的个数,类型不同的方法定义,此时编译器会根据用户调用方法是传入的参数个数和类型选择出对应的同名函数中匹配的,而Go语言不支持此种重载,因为Go语言中一种类型中方法定义不可重名。
至于多态,后文详述。

func (a *A) Call() {
	a.attr = 100
	fmt.Println("call a")
}

// func (a A) Call() {
//	fmt.Println("call a")
// }

a := A{}
a.Call()  // 执行通过
fmt.Println(a.attr) // 打印100

是不是上文给人一个错觉,对于 注释掉的部分,与上文的定义是一样的?事实并非如此。
注意:
1,实际用户不可以同时定义二者,这是为何?
2,对于指针接收者修改a中的内容,原始对象改变,值接收者则不会

a之所以能够顺利调用接收者为 *A 类型的方法,是因为编译器为我们做的隐式转化。怎么转化呢?
换个写法:

A{}.Call()  // 执行报错
a.Call()  // -> (&a).Call()

那么理解接收者*A,和A不同呢?相信大家在定义Python的方类法时,第一个参数都会惯常为 self,表示当前对象,C++中的this,Go亦是如此。

a1 := new(A)
// 以下二者是等价的
a1.Call() // 点调用的选择器是一个语法糖
(*A).Call(a1)

此外,类似前文中的同名属性,同名方法呢?留给看官自行尝试。

回答上文为何不可同时定义同名Call,一个接收者为A另一个为 *A 。原因是编译器有一个隐式转化,对于接收者为 A 的方法,编译器会自动生成一个对应同名接收者为 *A 的方法。所以这个并不违背之前说的,不同类型可以定义同名的方法。
对于 value-receiver 会隐士转化一个 point-receiver 的同名定义!
调整代码:

func (a A) Call() {
	fmt.Println("call a")
}
// 以下正常执行
a2 := new(A)
A.Call(*a2)
(*A).Call(a2)

上文中的A{}.Call() 执行报错是因为只定义了 *A 接收者的方法,这个编译器不会隐士生成A 接收者的方法。所以A{}.Call() 不通过。

Interface

很多介绍都将Interface称之为Go语言的基石,原因何在呢?个人浅薄理解,Go是一种强类型面向对象的语言,那么OO的语言都会给大家一个概念:封装 -> 继承 -> 多态,且 封装继承 最终是为了 多态 准备。
Go语言中抽象封装何来呢?结构体就是上文中介绍的 struct,而对于 继承 Go采取的是一种更类似于组合 的形式。这二者有了,如何完成多态呢?

多态

在定义Interface时,只可以包含方法声明,不许包含方法实现,且不可以包含属性定义。对于struct只要实现了interface中申明的所有方法,就可以赋值给一个interface的对象。

type Reader interface {
    Read(p []byte) (n int, err error)
}
var r io.Reader
r = os.Stdin
r = bufio.NewReader(r)
r = new(bytes.Buffer)
f, _ := os.Open("test.txt")
r = bufio.NewReader(f)

对比理解interface很像C++中的vuirtal class 虚基类。至此Go语言的面向对象最后一环多态得已落成。
解读一个示例:

type Intger int
func (a Intger) Less(b Intger) bool {
	return a < b
}
func (a *Intger) Add(b Intger) int {
	*a += b
}
// 定义接口
type LessAdder inferface {
	Less(b Intger) bool
	Add(b Intger)
}

//接口赋值
var a Intger = 1
var b LessAdder = a  // 正确与否?
var b LessAdder = &a // 正确与否?
上面两个都不正确!!? 前文提及,对于 value-receiver 会隐式生成一个 point-receiver 的同名定义么? 没错,但是这个隐士转化,并不是给 一个 point对象直接调用方法时做 方法的隐士转化!!注意上文细节,那里的隐士转化时 a.Call 隐式转为 (&a).Call。

那这个同名的隐士转化是做何用呢?

interface!当要把一个 value 对象赋值给一个 interface时,要求对象必须是 addressable ,必须是指针类型,因此才会有自动的 隐式生成 point-receiver 的同名方法,但是实际 value 对象符合 interface 定义直接执行时,仍旧是做 value-receiver 的 Call 调用.

说明参考:https://stackoverflow.com/questions/45652560/interfaces-and-pointer-receivers
具体代码请移步

反射

由于interface可以指向任意一个实现了其中定义方法的struct,或者说任意一个实现了interface中方法的类型,那怎样在Coding 中去断言当前的interface对象究竟是何种类型呢?

v , ok := varI.(T)  // 判定varI是否是T类型,如果是则ok为true,value为对应当前interface指向的 *T

或者,我们对varI 会进行不同类型判定后做不同处理则可以:

switch v := varI.(type) {
case *Tpye1:
	doSomething1(v)
case *Type2:
	doSomething2(v)
}

空interface

许多内置方法的参数,都会以interface{} 作为参数,此时interface{} 没有申明任何一个方法,因此可以接纳任意类型,换而言之:空interface是一切类型的基类
诸如:

// fmt 中 Println 函数的定义
func Println(a ...interface{}) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}

前文提过Go语言所有的赋值均是 值拷贝,那么对于常见的 9种非基础类型:
1,所有的数组类型,均是全量的值复制拷贝,传递长度为 len(array) * Sizeof(elem)
2,字符串类型string,slice,interface传递的是底层定义的对象,slice在笔记1中提过是24Byte,string是 16 Byte,interface是16Byte
3,map,channel,func,pointer均传递的是指针,8Byte
4,struct传递的是所有字段的副本,因此长度是所有字段的长度之和
以上的长度以64位机器计算。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值