面向对象编程篇
go语言面向对象编程,并不像传统面向对象编程中有继承、封装、多态、函数重载等概念;
go语言面向对象编程非常的 简单优雅,,结构(struct)、方法(method)、接口(interface)
浑然一体,共同支持着 面向对象编程
一、结构体 struct
go语言支持基本类型组合功能-struct结构类型,struct结构类型看似简单,实则能实现很强大的复杂功能
1. 定义
type <name> struct {}
type Rect struct {
x, y float64
width, height float64
}
2. 初始化
2.1 初始化可以有多种方式:
rect1 := new(Rect) //返回一个指向类型的指针
rect2 := &Rect{} //初始化为一个空的结构类型
rect3 := &Rect{1, 1, 2, 2} //按顺序进隐式初始化
rect4 := &Rect{width:2, height:2} //显示初始化
ps: 1)对结构初始化的时候,建议对结构取地址符号,主要原因是struct类型是值传递,在其它函数方法内修改struct,并不会影响struct;
2)未进行显式初始化的变量都会被初始化为该类型的零值
2.2 对象的创建也可使用一个 全局的创建函数来完成,表示“构造函数”, 一般命名为 NewXXX
func NewXXX(x, y, width, height float64) *Rect {
return &Rect{x, y, width, height}
}
3. 匿名结构
3.1 匿名结构就是 只有类型没有名称的结构
3.2 定义及初始化
3.2.1 直接定义及初始化
a := &struct {
name string
age int
}{
name: "xuww",
age: 20,
}
3.2.2 以嵌入结构形式
type X struct {
Name string
}
type Y struct {
X //以X类型作为字段名
Age int
}
s := &Y {X:X{"xuww"}, Age:20}
3.2.3 以嵌入匿名结构形式
type Y struct {
X struct {
Name string
}
Age int
}
4. 匿名字段
4.1 匿名字段就是 只有类型没有名称的字段
匿名字段 本质上是 定义了以某个类型作为名称 的字段
5. 嵌入结构
虽然go语言中,没有继承和多态, 但是通过嵌入结构,可以实现类似功能
type Base struct {
Name string
}
func (b *Base) Foo() {
fmt.Println(b.Name, "from Base Foo()")
}
func (b *Base) Bar() {
fmt.Println(b.Name, "from Base Bar()")
}
type Sub struct {
Base //"继承" Base 所有属性
Age int
}
func (s *Sub) Bar() { //在Sub方法中,重写Bar()函数,"多态"
s.Name = "Hello"
fmt.Println(s.Name, "from Sub Bar()")
}
6. 同名字段
同一级别,不能出现同名字段
不同级别,可以出现同名字段
type A struct {
B // 结构体A中name级别要高于结构体B中name级别, 所以可同名
//C // B C 结构体中 name 是同一级别,同名字段会出错
name string
}
type B struct {
name string
}
type C struct {
name string
}
低级别的同名字段被隐藏,使用时需要显示调用
类似: a.B.Name
7. 反引号
在结构体的类型声明的字段后面我们可以添加一个由反引号“`”括起来的标签,供reflect使用:
二、方法
go语言面向对象编程非常简洁直观,通过对 方法施加的目标(对象)显示传递,可以很清晰的看到是 谁的方法,
不像c++在面向对象编程时,会隐藏this指针。
1. 方法概述
1.1 方法和函数类似,只是多了receiver,编译器就是根据receiver,来判断是谁的方法
1.2 go语言中,不存在方法重载
1.3 receiver也是遵循正常的参数规则,如果是以 值类型传递,只能得到对象的一个拷贝(副本),
所以说如果需要修改对象的时候, 必须用 指针传递
1.4 在go语言中,可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法,
2. Method Value 与 Method Expresion
Method Value
已经声明了receiver,用变量或对象 来调用函数一样 调用方法
Method Expression
通过类型去调用方法, 把变量或对象 作为receiver,并作为第一个参数 传递给方法
type IT int
func (it *IT) Print() {
fmt.Println("IT Method")
}
var a IT
a.Print() //Method Value
(*IT).Print(&a) //Method Expression
3. 方法名称冲突
嵌入结构有 字段名称冲突, 方法也有 名称冲突,
处理的原则同字段名称冲突类似,即:
如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法;
如果同一对象 有同名方法(receiver + 函数名),编译会报错
三、接口
在其它面向对象编程语言中, 实现类需要明确声明自己实现了哪个接口,这种强制性不利于开发,
但是在go语言中,只要某个类型实现了 该接口的所有方法,即算实现该接口,无需显示声明实现了哪个接口,这称为 Structural Typing
1. 接口定义
接口是一个或多个方法签名的集合,接口只有方法声明,没有实现,没有数据字段
type USB interface {
Phone() string
Connect()
}
2. 接口赋值
接口赋值分为两种情况:
将对象实例赋值给接口
将一个接口赋值给另一个接口
2.1 将对象赋值给接口
type A interface {....}
type B struct {
Name string
}
//假设B类型实现了A接口
var a A = B{"hi"}
或者 var a A = &B{"hi"}
ps:将对象赋值给接口时,会发生拷贝,而接口内部存储的黑丝指向之歌复制品的指针,
2.2 将一个接口赋值给另外一个接口
只要两个接口拥有相同的方法列表(次序不同不要紧),那么他们就是等同的,可以相互赋值;
可以将拥有超集接口 转换为 子集接口
3. 嵌入接口(有点像 继承)
接口可以匿名嵌入到其它接口中
type USB interface {
Phone() string
CONNECT //匿名接口嵌入
}
type CONNECT interface {
Connect()
}
4. 接口查询
查询某一接口 指向的对象实例 是否实现了 另外一接口
查询某一接口 指向的对象实例 是否是 某一类型
接口查询使用的是 ok-pattern模式来判断
type A interface {
Read()
Write()
}
type B interface {
Read()
}
type File struct {
Name string
}
func (f *File) Read() {
fmt.Println(f.Name, "Read")
}
func (f *File) Write() {
fmt.Println(f.Name, "Write")
}
var a A = &File{"Hi"}
var b B
if v, ok := a.(B); ok { //查询a接口指向的对象实例 是否实现了 B接口
fmt.Println("a has implement B interface")
b = v
}
if _, ok := a.(*File); ok { //查询a接口指向的对象实例 是否是 *File类型
fmt.Println("*File has implement A interface")
}
5. 类型查询
在go语言中,可以直截了当询问 接口 指向的对象实例 的类型
使用 switch-case模式来判断
例如:
var v1 interface{} = ...
switch v := v1.(type) {
case int: //判断类型
... // v就是 v1的值
case string:
default:
...
}
ps: 空接口 常常配合 type-switch使用
6. Any类型 --- 空接口
go语言中,空接口 非常有用
因为是空接口,所以任意类型都实现了空接口 interface{},
所以说 interface{} 是 Any类型
变量类型
函数参数类型
返回值类型
使用空接口使得程序有更大的扩展性
空接口通常结合 type-switch使用
有疑问加站长微信联系(非本文作者)