Golang OOP、继承、组合、接口
Golang OOP、继承、组合、接口
traditional oop concept
OOP(面向对象编程)是对真实世界的一种抽象思维方式,可以在更高的层次上对所涉及到的实体和实体之间的关系进行更好的管理。
流传很广的OOP的三要素是:封装、继承、多态。
对象:可以看做是一些特征的集合,这些特征主要由 属性 和 方法 来体现。
封装:划定了对象的边界,也就是定义了对象。
继承:表明了子对象和父对象之间的关系,子对象是对父对象的扩展,实际上,子对象“是”父对象。相当于说“码农是人”。从特征的集合这个意义上说,子对象包含父对象,父对象有的公共特征,子对象全都有。
多态:根据继承的含义,子对象在特性上全包围了父对象,因此,在需要父对象的时候,子对象可以替代父对象。
java 的oop: extends和implements
extends可以理解为全盘继承了父类的功能
Class Human {
name:string
age:int
function eat(){}
function speak(){}
}
Class Man extends Human {
function fish(){}
function drink(){}
}
implements可以理解为为这个类附加一些额外的功能
举个例子,Animal是一个父类,cat,dog,bird,insect都extends了Animal,
但是cat,dog,bird还可以implements比如run,shout这些interface,bird,insect可以implements比如fly这些interface
interface是定义接口的关键字,所谓的接口说白了就是一个规则,用电脑的USB插口打个比方
public interface USB接口{
这个接口只能插标准USB接口();/*所谓接口中的方法就是定义了个规范,而我这个标准USB接口中要插鼠标还是优盘,那就是在你的实现类中根据具体情况来看了*/
}
而implement就是实现接口的关键字,你要是现实了一个接口,那就必须实现里边的方法,接着上边的例子说
public class 类名 implement USB接口{
//现在就要具体实现刚才接口中定义的方法了,
接口中的方法(){
我这个接口是插鼠标的;
}
就是在你的类的原来的基础上又引进了别的方法,好比电脑,有触摸板,但引进个USB接口我可以插鼠标,可以插音箱等,是功能扩展
extendes,是继承的关键字,是子类继承父类的功能和属性
,还用电脑打比方,电脑是一个类,有显示器,键盘这些属性,笔记本电脑也是一个类,它具有电脑的全部属性,但是比电脑又多了其他属性和功能,笔记本电脑是从电脑那里派生出来的,这样,我们称电脑是父类,笔记本是子类,同理台式机也是电脑的子类,一个类可以有多个子类,但一个类只能有一个直接父类
interface和extendes的区别
interface实现接口必须实现接口中的全部方法
extendes是继承了父类的属性和方法,但可能永远都用不上
Golang 的 OOP, golong 使用的是结合而不是继承
从下边的代码可以看出,不能像java一样,把以human为参数的函数直接用于student, 或者说不能 h = s
但是,human的方法可以用于s 比如 func(h Human){fmt.Println(h.name)}
这既不同于 Java 类语言的行为,也不同于 prototype 链式继承的行为,Golang 叫做 Embedding,这像是一种寄生关系:Human 寄生在 Student 中,但仍保持一定程度的独立。
package main
import "fmt"
func main(){
var h Human
s := Student{Grade: 1, Major: "English", Human: Human{Name: "Jason", Age: 12, Being: Being{IsLive: true}}}
fmt.Println("student:", s)
fmt.Println("student:", s.Name, ", isLive:", s.IsLive, ", age:", s.Age, ", grade:", s.Grade, ", major:", s.Major)
//h = s // cannot use s (type Student) as type Human in assignment
fmt.Println(h)
//Heal(s) // cannot use s (type Student) as type Being in argument to Heal
// HealHuman(s) // # command-line-arguments
//./10_1_oop.go:13:14: cannot use s (type Student) as type Human in argument to HealHuman
HealHuman(s.Human)
Heal(s.Human.Being) // true
s.Drink()
s.Eat()
}
type Car struct {
Color string
SeatCount int
}
type Being struct {
IsLive bool
}
type Human struct {
Being
Name string
Age int
}
func (h Human) Eat(){
fmt.Println("human eating...")
h.Drink()
}
func (h Human) Drink(){
fmt.Println("human drinking...")
}
func (h Human) Move(){
fmt.Println("human moving...")
}
type Student struct {
Human
Grade int
Major string
}
func (s Student) Drink(){
fmt.Println("student drinking...")
}
type Teacher struct {
Human
School string
Major string
Grade int
Salary int
}
func (s Teacher) Drink(){
fmt.Println("teacher drinking...")
}
type IEat interface {
Eat()
}
type IMove interface {
Move()
}
type IDrink interface {
Drink()
}
func Heal(b Being){
fmt.Println(b.IsLive)
}
func HealHuman(h Human){
fmt.Println(h.Being)
}
// output
student: {{{true} Jason 12} 1 English}
student: Jason , isLive: true , age: 12 , grade: 1 , major: English
{{false} 0}
{true}
true
student drinking...
human eating...
human drinking...
Golang 的接口
我们从接口产生的原因来考虑。
代码处理的是各种数据。对于强类型语言来说,非常希望一批数据都是单一类型的,这样它们的行为完全一致。但世界是复杂的,很多时候数据可能包含不同的类型,却有一个或多个共同点。这些共同点就是抽象的基础。单一继承关系解决了 is-a 也就是定义问题,因此可以把子类当做父类来对待。但对于父类不同但又具有某些共同行为的数据,单一继承就不能解决了。单一继承构造的是树状结构,而现实世界中更常见的是网状结构。
于是有了接口。接口是在某一个方面的抽象,也可以看做具有某些相同行为的事物的标签。
但不同于继承,接口是松散的结构,它不和定义绑定。从这一点上来说,Duck Type 相比传统的 extends 是更加松耦合的方式,可以同时从多个维度对数据进行抽象,找出它们的共同点,使用同一套逻辑来处理。
Java 中的接口方式是先声明后实现的强制模式,比如,你要告诉大家你会英语,并且要会听说读写,你才具有英语这项技能。
interface IEnglishSpeaker {
ListenEnglish()
ReadEnglish()
SpeakEnglish()
WriteEnglish()
}
Golang 不同,你不需要声明你会英语,只要你会听说读写了,你就会英语了。也就是实现决定了概念:如果一个人在学校(有School、Grade、Class 这些属性),还会学习(有Study()方法),那么这个人就是个学生。
Duck Type 更符合人类对现实世界的认知过程:我们总是通过认识不同的个体来进行总结归纳,然后抽象出概念和定义。这基本上就是在软件开发的前期工作,抽象建模。
相比较而言, Java 的方式是先定义了关系(接口),然后去实现,这更像是从上帝视角先规划概念产生定义,然后进行造物。
因为 interface 和 object 之间的松耦合,Golang 有 type assertion 这样的方式来判断一个接口是不是某个类型:
value, b := interface.(Type) ,value 是 Type 的默认实例;b 是 bool 类型,表明断言是否成立。
interface 判断
// 接上面的例子
v1, b := interface{}(s).(Car)
fmt.Println(v1, b)
v2, b := interface{}(s).(Being)
fmt.Println(v2, b)
v3, b := interface{}(s).(Human)
fmt.Println(v3, b)
v4, b := interface{}(s).(Student)
fmt.Println(v4, b)
v5, b := interface{}(s).(IDrink)
fmt.Println(v5, b)
v6, b := interface{}(s).(IEat)
fmt.Println(v6, b)
v7, b := interface{}(s).(IMove)
fmt.Println(v7, b)
v8, b := interface{}(s).(int)
fmt.Println(v8, b)
上面的代码中,使用空接口 interface{} 对 s 进行了类型转换,因为 s 是 struct,不是 interface,而类型断言表达式要求点号左边必须为接口。
{ 0} false
{false} false
{{false} 0} false
{{{true} Jason 12} 1 English} true
{{{true} Jason 12} 1 English} true
{{{true} Jason 12} 1 English} true
<nil> false
false
常用的基于interface的用法 类似范型
s1 := Student{Grade: 1, Major: "English", Human: Human{Name: "Jason", Age: 12, Being: Being{IsLive: true}}}
s2 := Student{Grade: 1, Major: "English", Human: Human{Name: "Tom", Age: 13, Being: Being{IsLive: true}}}
s3 := Student{Grade: 1, Major: "English", Human: Human{Name: "Mike", Age: 14, Being: Being{IsLive: true}}}
t1 := Teacher{Grade: 1, Major: "English", Salary: 2000, Human: Human{Name: "Michael", Age: 34, Being: Being{IsLive: true}}}
t2 := Teacher{Grade: 1, Major: "English", Salary: 3000, Human: Human{Name: "Tony", Age: 31, Being: Being{IsLive: true}}}
t3 := Teacher{Grade: 1, Major: "English", Salary: 4000, Human: Human{Name: "Ivy", Age: 40, Being: Being{IsLive: true}}}
drinkers := []IDrink{s1, s2, s3, t1, t2, t3}
for _, v := range drinkers {
switch t := v.(type) {
case Student:
fmt.Println(t.Name, "is a Student, he/she needs more homework.")
case Teacher:
fmt.Println(t.Name, "is a Teacher, he/she needs more jobs.")
default:
fmt.Println("Invalid Human being:", t)
}
}
output:
Jason is a Student, he/she needs more homework.
Tom is a Student, he/she needs more homework.
Mike is a Student, he/she needs more homework.
Michael is a Teacher, he/she needs more jobs.
Tony is a Teacher, he/she needs more jobs.
Ivy is a Teacher, he/she needs more jobs.
why no longer oop in go ?
James Gosling :
I once attended a Java user group meeting where James Gosling (Java’s inventor) was the featured speaker.
During the memorable Q&A session, someone asked him: “If you could do Java over again, what would you change?”
“I’d leave out classes,” he replied. After the laughter died down, he explained that the real problem wasn’t classes per se, but rather implementation inheritance (the extends relationship). Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.