众所周知Golang并不是一个面向对象的语言,但他却可以用别的方式去实现.
一个例子,假设一个打卡系统,有入门和出门的操作,我们首先定义Student和Teacher两个对象(在Golang中为结构体)(并且有一个ClassInfo的类嵌套).他们拥有学号,名字,班级信息三个属性.

type ClassInfo struct {
	Class int
	No    int
}

type Student struct {
	Class ClassInfo
	Name  string
	id    int
}

type Teacher struct {
	Class ClassInfo
	Name  string
	id    int
}

然后给他们分别写各自的入门和出门的方法.

func (stu Student) inRoom() {
	fmt.Println(stu.Name, "student in room")
	ch <- stu.Name
}
func (stu Student) outRoom() {
	fmt.Println(stu.Name, "student out room")
	ch <- stu.Name
}
func (tea Teacher) inRoom() {
	fmt.Println(tea.Name, "teacher in room")
	ch <- tea.Name
}
func (tea Teacher) outRoom() {
	fmt.Println(tea.Name, "teacher out room")
	ch <- tea.Name
}

这样的话我们回到main函数中,我们声明两个对象,只能这样一个一个调用他们的方法.

func main() {
	var stu Student = Student{
		Class: ClassInfo{Class: 1, No: 1},
		Name:  "John",
		id:    223344,
	}
	var tea Teacher = Teacher{
		Class: ClassInfo{Class: 2, No: 0},
		Name:  "miku",
		id:    000000,
	}

	stu.inRoom()
	stu.outRoom()
	tea.inRoom()
	tea.outRoom()
	
	....

如果对象的方法多起来,那么一个一个调用是非常愚蠢的.
所以我们可以为他们的方法做一个打包.

func doStudent(stu Student) {
	stu.inRoom()
	stu.outRoom()
}
func doTeacher(tea Teacher) {
	tea.inRoom()
	tea.outRoom()
}

这样只用调用这一个doStudent或者doTeacher就好了,但这样并不够好,如果类型也变得更多了该怎么办?来了个书记struct,校长struct....
所以我们必须定义一个接口,就像下面这样.

type Person interface {
	inRoom()
	outRoom()
}

这个Person接口很简单,他只用实现入门和出门两个操作就行. 然后像上面一样对Person所做的事情做一个打包方便调用

func PersonCheckAll(p Person) {
	p.inRoom()
	p.outRoom()
}

那么主函数就变成这样写, 我们定义一个Person实例, 当他需要是什么类型时,他就变成什么类型, 有点像OOP的 Person p = new Student() 不是么,实现多态,这就是这里接口的作用.

	var personImplement Person
	personImplement = stu
	personImplement.inRoom()
	personImplement.outRoom()
	PersonCheckAll(personImplement)

其实这个例子并不恰当,因为入门和出门并不是什么需要并发的东西,为了显示Golang优雅的并发,我们考虑Teacher和Student一起,有大量的人一起入门的情况.
我们只需要声明一个Person空接口,这个空接口其实有点泛型的意思,一个能容纳下不同类型的list. 我们把tea和stu都扔进去. 并且遍历入门打卡.

	p := [...]Person{stu, tea}

	for _, v := range p {
		go v.inRoom()
	}
	for i := 0; i < len(p); i++ {
		msg := <-ch // 等待信道返回消息。
		fmt.Println("finish", msg)
	}	

go关键字能够轻松开启一个协程,我们使不同对象的入门操作变为并发了.
并且这里回应两上面inRoom,outRoom函数中的ch <- tea.Name是什么意思,这是将此协程的信息通过channel进行传输.

完整的代码:

package main

import (
	"fmt"
)

type Person interface {
	inRoom()
	outRoom()
}

type ClassInfo struct {
	Class int
	No    int
}

type Student struct {
	Class ClassInfo
	Name  string
	id    int
}

type Teacher struct {
	Class ClassInfo
	Name  string
	id    int
}

func (stu Student) inRoom() {
	fmt.Println(stu.Name, "student in room")
	ch <- stu.Name
}
func (stu Student) outRoom() {
	fmt.Println(stu.Name, "student out room")
	ch <- stu.Name
}

// func doStudent(stu Student) {
// 	stu.inRoom()
// 	stu.outRoom()
// }

func (tea Teacher) inRoom() {
	fmt.Println(tea.Name, "teacher in room")
	ch <- tea.Name
}
func (tea Teacher) outRoom() {
	fmt.Println(tea.Name, "teacher out room")
	ch <- tea.Name
}

// func doTeacher(tea Teacher) {
// 	tea.inRoom()
// 	tea.outRoom()
// }

func PersonCheckAll(p Person) {
	p.inRoom()
	p.outRoom()
}

var ch = make(chan string, 2)

func main() {
	var stu Student = Student{
		Class: ClassInfo{Class: 1, No: 1},
		Name:  "John",
		id:    223344,
	}
	var tea Teacher = Teacher{
		Class: ClassInfo{Class: 2, No: 0},
		Name:  "miku",
		id:    000000,
	}

	// var personImplement Person
	// personImplement = stu
	// personImplement.inRoom()
	// personImplement.outRoom()
	// PersonCheckAll(personImplement)

	// personImplement = tea
	// personImplement.inRoom()

	p := [...]Person{stu, tea}

	for _, v := range p {
		go v.inRoom()
	}
	for i := 0; i < len(p); i++ {
		msg := <-ch // 等待信道返回消息。
		fmt.Println("finish", msg)
	}
	fmt.Println("Done!")

}