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

  • 抽象
  • 封装
  • 继承
  • 多态
Is Go An Object Oriented Language?

那么问题来了

  1. Golang是OOP吗?
  2. 使用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())
}
structclass

2、可见性. 这个遵循Go语法的大小写的特性

*rectreceiverreceiver
func (r *rect) area() int {
    return r.width * r.height
}
func (r rect) area() int {
    return r.width * r.height
}

这其中有什么区别和联系呢?

简单来说, Receiver可以是值传递, 还是可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。

Receiver*rectr.width(*r).width

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 
隐式转换显式声明

上面的例子改成下面的方式就可以了.

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.

Composition
Compostion匿名组合(Pseudo is-a)非匿名组合(has-a)
is-ahas-a

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
structstruct

从上面例子可以, 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

这里要说的有几点:

StudentHuman
fmt.Println("Student age:", mark.age) //输出: Student age: 25

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

fmt.Println("Student age:", mark.Human.age) //输出: Student age: 25
Studentnamemark.nameStudentname
fmt.Println("Student name:", mark.name) //输出:Student Name: student name
StudentHumanSayHi()
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
Pseudo is-a
匿名组合多态
package main

type A struct{
}

type B struct {
	A  //B is-a A
}

func save(A) {
	//do something
}

func main() {
	b := new(B)
	save(*b);  
}

Output

cannot use *b (type B) as type A in argument to save

还有一个面试题的例子

type People struct{}

func (p *People) ShowA() {
	fmt.Println("showA")
	p.ShowB()
}
func (p *People) ShowB() {
	fmt.Println("showB")
}

type Teacher struct {
	People
}

func (t *Teacher) ShowB() {
	fmt.Println("teacher showB")
}

func main() {
	t := Teacher{}
	t.ShowA()
}

输出结果是什么呢?

Output

ShowA
ShowB

Effective Go Says:

There's an important way in which embedding differs from subclassing. When we embed a type, the methods of that type become methods of the outer type, but when they are invoked the receiver of the method is the inner type, not the outer one

TeacherPeopleTeacherShowA()ShowA()ShowBreceiver*People*TeacherembeddingPseudo is-a

4、 "多继承"的问题

package main

import "fmt"

type School struct {
	address string
}

func (s *School) Address() {
	fmt.Println("School Address:", s.address)
}

type Home struct {
	address string
}

func (h *Home) Address() {
	fmt.Println("Home Address:", h.address)
}

type Student struct {
	School
	Home
	name string
}

func main() {
	mark := Student{School{"aaa"}, Home{"bbbb"}, "cccc"}
	fmt.Println(mark)
	mark.Address()
	fmt.Println(mark.address)

	mark.Home.Address()
	fmt.Println(mark.Home.address)
}

输出结果:

30: ambiguous selector mark.Address
31: ambiguous selector mark.address
Embedding匿名字段
mark.Home.Address()
Embedding valueEmbedding pointer
package main

import (
	"fmt"
)

type Person struct {
	name string
}

type Student struct {
	*Person
	age int
}

type Teacher struct {
	Person
	age int
}

func main()  {
	s := Student{&Person{"student"}, 10}
	t := Teacher{Person{"teacher"}, 40}
	fmt.Println(s, s.name)
	fmt.Println(t, t.name)
}

Output

{0x1040c108 10} student
{{teacher} 40} teacher
Embedding valueEmbedding  pointer

三. Interface

CompositeGolangInterface

下面是我工程里面一段代码的简化:

package main

import (
	"fmt"
)

type Check interface {
	CheckOss()
}

type CheckAudio struct {
	//something
}

func (c *CheckAudio) CheckOss() {
	fmt.Println("CheckAudio do CheckOss")
}

func main() {
	checkAudio := CheckAudio{}

	var i Check

	i = &checkAudio //想一下这里为啥需要&

	i.CheckOss()
}

Composite
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}
ReaderWriterReadWriterReadWriterReaderWriter

尾声

至此, 基本说完了Golang的面向对象. 有哪里我理解的不对的地方, 请给我留言.

参考资料