Go面向对象
golang不是传统的面向对象编程,而是通过struct, method, interface, embedding实现面向对象的核心功能.
方法/method
go中没有类,但是可以为结构体定义方法.
方法就是一类带有特殊的 接收者参数 的函数.
只能为同一包内定义的类型的接收者申明方法,不能为其它包内定义的类型的接收者申明方法.
除了结构体还可以为非结构体申明方法,但是不能为内建类型申明方法.
方法有两种接收者,值接收者和指针接收者.
type Vertex struct {
...
}
值接收者:
值接收者操作的是值的副本.
func (v Vertex) MethodName() rType {
...
}
var v Vertex
v.MethodName() // 操作值的副本
# 使用指针接收者来调用值接收者的方法,编译器会自动做类型转换
vp := new(Vertex)
vp.MethodName() // 指针被解引用为值,(*vp).MethodName(),操作的是指针指向的值的副本.
指针接收者:
指针接收者,调用方法的时候操作的是该指针指向的值.
# 指针接收者的方法可以修改接收者指向的值
func (v *Vertex) MethodName() rType {
...
}
vp := new(Vertex)
vp.MethodName() // 操作实际的值
# 使用值接收者来调用指针接收者的方法,编译器会自动做类型转换.
var v Vertex
v.MethodName() // (&v).MethodName() , 操作的是实际的值
接口/Interface
接口是引用类型. zero value is nil.
接口类型是由一组方法签名定义的集合, 任何实现了这些方法的类型,自动被认为实现了这个接口。.
某个类型实现了该接口的所有方法签名,就算实现了该接口,无需显示申明实现了哪个接口.
接口可以匿名嵌入其它接口,或嵌入到结构中.
一般只有一个方法的接口命名为MethodName + ’er'.
接口存储两个数据接口:
- iTable, 包含存储值的类型信息以及和该值关联的方法.
- 指向存储的值的指针.
定义接口:
type Speaker interface {
Speak()
}
type Animal struct {
Name string
}
func (a Animal) Speak() {
...
}
值接收者方法:
接口类型的值为值或指针都可以调用该方法.
func (s SName) MethodName() {}
指针接收者方法:
接口类型的值必须为指针才能调用该方法.
func (s *SName) MethodName() {
...
}
s := &SName{...}
// i = &SName{...} //接口变量能保存实现了该接口的任意类型的对象
i = s
i.MethodName()
# 接口类型为值时,调用失败
var s SName
i = s
i.MethodName() // 调用失败
- 空接口:
所有类型都实现了空接口.
指定了零个方法的接口值被称为空接口,一般用来处理未知类型的值.
当接口储存的类型和对象都为nil,接口才为nil
以空接口为参数的函数可以接收任意类型的值作为参数.
如果一个函数返回空接口就可以返回任意类型的值.
var x interface{}
x = 43
x = "hello"
# 空接口可以保存任何类型的值
var i interface{}
# 空接口作为形参
func FuncName(i interface{}) {}
- 指针的类型断言/comma-ok断言:
断言接口值i保存了具体类型Type, 并将底层类型为Type的值赋予变量t.
只返回一个值,断言失败会触发panic.
t := i.(Type)
返回两个值,断言成功返回true, 失败返回false.
var x interface{} = "hello"
str, ok := x.(string) // ok=true
num, ok := x.(int) // ok=false 断言失败
// i.(type) 是固定写法
switch v := i.(type) {
case string:
fmt.Println("string")
case int:
fmt.Println("int")
default:
fmt.Println("unknown type")
}
- 嵌入接口:
接口中可以嵌入其它接口,这样实现了该接口的对象就隐式包含嵌入接口的所有方法.
type Reader interface {
Read()
}
type Writer interface {
Write()
}
type ReadWriter interface {
Reader
Writer
}
封装Encapsulation
小写字母开头的标识符(类型/属性/方法…)是未公开的,只能在当前包引用,不能在其它包引用。
可以通过创建工厂函数对外暴露未公开的标识符:
- 标识符才有公开或未公开属性,值没有
- 短变量声明操作符,有能力捕获引用的类型,并创建一个未公开的类型的变量;显示声明的变量不能引用未公开类型。
通过工厂函数返回未公开类型的值:
# packageA
type privateType Type
func New(args) privateType {
return privateType(args)
}
# packageB
import packageA
func main() {
b := packageA.New(args) // 返回一个值,而不是标识符
}
公开类型的未公开属性:
# packageA
type Public struct {
private Type
}
# packageB
import packageA
func main() {
b := packageA.Public{
private: value, // panic, 公开类型的未公开属性不能通过字面量直接赋值
}
}
类型嵌套中的未公开类型的公开属性会提升到外部类型:
# packageA
type inner struct { // inner private
In Type // In public
}
type Outer struct {
inner
Out Type
}
# packageB
import packageA
func main() {
b := packageA.Outer{
Out: value, // 外部类型中的未公开内部类型不能通过字面量直接赋值
}
b.In = value // 内部类型的公开属性提升到外部类型。
}
多态Polymorphism
golang多态通过interface实现。
形式参数是接口的函数,叫做多态函数.
func FuncName(iVar IName) {}
所有实现了接口的方法的实体类型,就可以作为参数传给多态函数.
type Speaker interface {
Speak()
}
实现1
type Dog struct{}
func (d Dog) Speak() {
fmt.Println("dog")
}
实现2
type Cat struct{}
func (c Cat) Speak() {
fmt.Println("cat")
}
多态函数
func makeSound(s Speaker) {
s.Speak()
}
func main() {
var d Dog
var c Cat
makeSound(d) // dog
makeSound(c) // cat
}
继承Inheritance
golang没有继承,但是可以通过结构体嵌套实现继承行为。
如果外部类型实现同名的属性或方法,就会覆盖内部类型的属性或方法(不过内部类型的属性和方法还在,可以通过内部类型访问)
内部类型实现的接口,也会自动提升到外部类型。只要内部类型实现了某接口,外部类型相当于也实现了该接口。
如果匿名字段实现了一个方法,那么包含这个匿名字段的结构也可以调用该方法.
type Inner struct {
in Type
}
func (i *Inner) InnerMethod() {}
type Outer struct {
Inner // 只需要类型名称,不需要声明变量
out Type
...
}
outer := Outer{}
outer.Inner.InnerMethod() //始终可以访问内部类型的方法,即使外部类型实现同名方法
outer.Inner.in // 同上
outer.InnerMethod() //如果外部类型没有实现同名方法,就是内部类型方法,否则是外部类型方法
outer.in // 同上
反射Reflection
反射就是检查程序在运行时的状态.
使用反射一般使用标准库reflect.
Go 的反射通过 reflect.TypeOf() 和 reflect.ValueOf() 在运行时检查或操作对象,是实现泛型、序列化、ORM、接口断言等动态功能的基础。
import "reflect"
x := 123
t := reflect.TypeOf(x) // int
v := reflect.ValueOf(x) // 123
k := t.Kind() // reflect.Int