Skip to main content

struct

go语言支持类的操作,但是没有class关键字,使用struct来模拟类

封装

struct的属性和方法

package main

import "fmt"

//Person类,绑定方法 Eat Run Laugh
//public private =》 golang变量首字母大小写控制属性的可见性

/*
c++写法
class Person{
public :
string name
int age
public :
Eat() {
}
}
*/
type Person struct {
//成员属性
name string
age int
gender string
score float64
}

//go: 在外面绑定方法
//值接受者
func (p Person) Eat() {
fmt.Printf("%s is Eating\n",p.name)
//类的方法可以使用自己的成员
}

func main() {
p:=Person{
name: "Lucy",
age: 18,
gender: "famale",
score: 89,
}
p.Eat() //Lucy is Eating
}

struct值接收者和指针接收着的区别

package main

import "fmt"

type Person struct {
//成员属性
name string
age int
gender string
score float64
}

//go: 在外面绑定方法
//值接受者只是Person的拷贝
func (p Person) Eat() {
p.name = "Scott"
//类的方法可以使用自己的成员
}

//指针接受者还可以修改成员变量
func (p *Person) Eat2() {
p.name = "Scott"
//类的方法可以使用自己的成员
}

func main() {
p:=Person{
name: "Lucy",
age: 18,
gender: "famale",
score: 89,
}
p.Eat()
//name修改失败
fmt.Println(p.name) //Lucy

p1:= &Person{
name: "Lucy1",
age: 18,
gender: "famale",
score: 89,
}
p1.Eat2()
fmt.Println(p1.name) //Scott
}

继承:Go只有组合没有继承

继承是面向对象编程中的重要概念,在中文 wiki 中是这样解释的:

继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别B“继承自”另一个类别A,就把这个B称为“A的子类”,而把A称为“B的父类别”也可以称“A是B的超类”。

实际上 Go 中并不存在 「类」的概念,所以严格意义上说在 Go 中是没有「继承」一说的,只是在实践过程中可以通过 “组合” 的方式来实现 OOP 中 “继承” 所需的特性。

那什么是「组合」?又是如何通过组合实现 「继承」 的特性呢?下面来看下 Go 中常见的几种组合方式。

如何使用「组合」

(1)结构体中内嵌结构体

结构体的成员可以是另一个结构体,通过这种内嵌结构体的方式实现字段、方法的复用。看个例子,假设有 Animal 和 Dog 结构体如下。

type Animal struct{
name string
}

func (a Animal) Name() string{
return a.name
}

type Dog struct{
Animal
}

Dog 结构体内嵌了 Animal 结构体,因此可以使用 Animal 结构体的 name 字段以及 Name 方法,从感官上类似「继承」的效果,但是实际上不需要类似 Java 的 “extends” 关键字来显式声明继承关系。

func main() {
dog := Dog{}
dog.name = "dog"
fmt.Println(dog.name)
fmt.Println(dog.Name())
}

(2)结构体中内嵌接口

结构体的成员可以是接口,用以限制该成员必须实现约定的方法。看个例子如下。

type Worker interface{
Work()
}

type Engineer struct{
}

func (e *Engineer) Work() {
fmt.Println("do engineer work")
}

type Company struct{
Workers []Worker
}

func (c *Company) RecruitWorker(w Worker){
c.Workers = append(c.Workers, w)
}

func main() {
e:= &Engineer{}
c := &Company{
Workers: make([]Worker,0),
}

c.RecruitWorker(e)
for _, v := range c.Workers{
v.Work()
}
}

Company 结构体中的成员为接口 Worker,那么任意实现了 Work 方法的结构体都可以成为 RecruitWorker 方法的入参。

(3)接口中内嵌接口

接口中内嵌接口是针对「方法」的组合。在 Go 实践中,一个接口通常不会定义太多方法,例如 Reader 接口只有 Read 一个方法。接口保持「简洁」的特性就可以做到方便的组合,例如下文中的 ReadWriter 接口就是由 Reader 接口和 Writer 接口组合而成的。

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

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

// ReadWriter is the interface that groups the basic Read and Write methods.
type ReadWriter interface {
Reader
Writer
}
注意

综上,Go 的最佳实践中推荐的是「组合」的方式,而不是其他 OOP 语言中所说的「继承」,或许在日常工作中你不需要太在意这些细节,但是脑海里还是要清楚知道二者区别的。

(4)example

package main

import "fmt"

type Human struct {
//成员属性
name string
age int
gender string
}

//go: 在外面绑定方法
func (this *Human) SayHello() {
fmt.Println("hello, this is ",this.name)
//类的方法可以使用自己的成员
}

//定义一个学生类 去嵌套Human
type Student struct {
hum Human //包含Human类型的变量 此时是类的嵌套
school string
score float64
}

//定义一个老师,去"继承"Human
type Teacher struct {
Human //直接写Human类型,没有字段名
subject string //学科
}


func main() {
s1:=Student{
hum: Human{
name: "Lily",
age: 17,
gender: "girl",
},
school: "黄冈中学",
score: 98.5,
}
fmt.Println("s1.name:",s1.hum.name)
fmt.Println("s1.school:",s1.school)

t1:=Teacher{}
t1.subject="English"
//下面这几个字段都继承于Human
t1.name="张老师"
t1.age=35
t1.gender="woman"
fmt.Println("t1:",t1)
t1.SayHello()
//"继承"的时候,虽然我们没有定义字段名字,但是会自动创建一个默认的同名字段
//之所以这么设计 是为了在子类中依然可以操作父类 因为子类父类有可能出现同名字段
//下面是等效的
fmt.Println("t1.Human.name:",t1.Human.name) //我们可以这样调用
fmt.Println("t1.name:",t1.name)//也可以这样调用
//同理下面
t1.Human.SayHello()
t1.SayHello()
}

访问权限

在go语言中,不同于c系列和java,权限都是通过首字母的大小来控制的

  1. import 如果包名不同 那么只有大写字母是public的
  2. 对于类里面的成员和方法 =》只有首字母大写才能在其他包中被使用
  3. 同一个包中 所有都是可见的

多态

在讲多态之前,我们必须要了解 interface 这个数据类型

C语言的多态需要父子继承关系

go语言不需要继承:只要实现了相同的接口即可

空接口可以接受任意的数据类型,非空接口需要实现其所有方法才能进行赋值

示例

package main

import "fmt"

//实现go多态,需要实现定义接口
//人类的武器发起攻击,不同登记子弹效果不同

//定义一个接口,注意类型是interface
type IAttack interface {
//接口函数可以由多个,但是只能有函数原型 不可以实现
Attack()
}

//定义role:小鱼人
type Fizz struct {
lever int
damage int
}

func (h Fizz) Attack() {
fmt.Println("我是:小鱼人,等级为:",h.lever,"基础伤害为:",h.lever*h.damage)
}

//定义role:阿卡丽
type Akali struct {
lever int
damage int
}

func (h Akali) Attack() {
fmt.Println("我是:阿卡丽, 等级为:",h.lever,"基础伤害为:",h.lever*h.damage)
}

//定义一个多态的通用接口,传入不同的对象,调用同样的方法,实现不同的效果=》多态
func DoAttack(a IAttack) {
a.Attack()
}

func main() {
var role IAttack //定义一个包含Attack()方法的变量
fizz := Fizz{
lever: 1,
damage: 500,
}

fizz.Attack()
role = &fizz //注意:接口需要指针类型来赋值
role.Attack()

akali:= Akali{
lever: 1,
damage: 555,
}
akali.Attack()
role= &akali
role.Attack()

fmt.Println("多态.....")
//可以看到全程没有任何继承关系
DoAttack(&fizz)
DoAttack(&akali)
}

总结

  1. 定义一个接口,里面设计好需要的接口,可以有多个
  2. 任何实现了这个接口的类,都可以赋值给这几个接口,从而实现多态
  3. 多个类之间不需要继承关系
  4. 如果interface中定义了多个方法 那么你必须实现所有的方法 才算实现了这个接口 才可以赋值

测试:如果我们把Attack()改为Attack1(), 发现赋值不了了

快速了解struct