抽象
封装
继承
多态
其实有个问题Is Go An Object Oriented Language?, 随便谷歌了一下, 你就发现讨论这个的文章有很多:
那么问题来了
Golang是OOP吗?
使用Golang如何实现OOP?
一. 抽象和封装抽象和封装就放在一块说了. 这个其实挺简单. 看一个例子就行了.
type rect struct { width int height int } func (r *rect) area() int { return r.width * r.height } func main() { r := rect{width: 10, height: 5} fmt.Println("area: ", r.area()) }完整代码
要说明的几个地方:
1、Golang中的struct和其他语言的class是一样的.
2、可见性. 这个遵循Go语法的大小写的特性
3、上面例子中, 称*rect为receiver. 关于receiver 可以有两种方式的写法:
func (r *rect) area() int { return r.width * r.height } func (r rect) area() int { return r.width * r.height }这其中有什么区别和联系呢?
简单来说, Receiver可以是值传递, 还是可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。
4、当Receiver为*rect指针的时候, 使用的是r.width, 而不是(*r).width, 是由于Go自动帮我转了,两种方式都是正确的.
5、任何类型都可以声明成新的类型, 因为任何类型都可以有方法.
type Interger int func (i Interger) Add(interger Interger) Interger { return i + interger }6、虽然Interger是从int声明而来, 但是这样用是错误的.
var i Interger = 1 var a int = i //cannot use i (type Interger) as type int in assignment这是因为Go中没有隐式转换(写C++的同学都会特别讨厌这个, 因为编译器背着我们干的事情太多了). Golang中类型之间的相互赋值都必须显式声明.
上面的例子改成下面的方式就可以了.
var i Interger = 1 var a int = int(i) 二. 继承(Composition)说道继承,其实在Golang中是没有继承(Extend)这个概念. 因为Golang舍弃掉了像C++, Java的这种传统的、类型驱动的子类。
Go Effictive says:
Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.
换句话说, Golang中没有继承, 只有Composition.
Golang中的Compostion有两种形式, 匿名组合(Pseudo is-a)和非匿名组合(has-a)
注: 如果不了解OOP的is-a和has-a关系的话, 请自行google.
1. has-a package main import ( "fmt" ) type Human struct { name string age int phone string } type Student struct { h Human //非匿名字段 school string } func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } func (s *Student) SayHi() { fmt.Printf("Hi student, I am %s you can call me on %s", s.h.name, s.h.phone) } func main() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} fmt.Println(mark.h.name, mark.h.age, mark.h.phone, mark.school) mark.h.SayHi() mark.SayHi() }Output
Mark 25 222-222-YYYY MIT Hi, I am Mark you can call me on 222-222-YYYY Hi student, I am Mark you can call me on 222-222-YYYY完整代码
这种组合方式, 其实对于了解传统OOP的话, 很好理解, 就是把一个struct作为另一个struct的字段.
从上面例子可以, Human完全作为Student的一个字段使用. 所以也就谈不上继承的相关问题了.我们也不去重点讨论.
2. is-a(Pseudo)----Embedding type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string } func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } func main() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} fmt.Println(mark.name, mark.age, mark.phone, mark.school) mark.SayHi() }Output
Mark 25 222-222-YYYY MIT Hi, I am Mark you can call me on 222-222-YYYY完整代码
这里要说的有几点:
1、字段
现在Student访问Human的字符, 就可以直接访问了, 感觉就是在访问自己的属性一样. 这样就实现了OOP的继承.
但是, 我们也可以间接访问:
fmt.Println("Student age:", mark.Human.age) //输出: Student age: 25这有个问题, 如果在Student也有个字段name, 那么当使用mark.name会以Student的name为准.
fmt.Println("Student name:", mark.name) //输出:Student Name: student name完整代码
2、方法
Student也继承了Human的SayHi()方法
当然, 我们也可以重写SayHi()方法:
type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string name string } func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } func (h *Student) SayHi() { fmt.Println("Student Sayhi") } func main() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT", "student name"} mark.SayHi() }Output
Student Sayhi完整代码
3、为什么称其为Pseudo is-a呢?