golang面向对象分析

说道面向对象(OOP)编程, 就不得不提到下面几个概念:

抽象

封装

继承

多态

其实有个问题Is Go An Object Oriented Language?, 随便谷歌了一下, 你就发现讨论这个的文章有很多:

reddit

那么问题来了

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.age) //输出: Student age: 25

但是, 我们也可以间接访问:

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()方法

mark.SayHi() // 输出: Hi, I am Mark you can call me on 222-222-YYYY

当然, 我们也可以重写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呢?

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssfps.html