golang面向对象编程思想


一、抽象

就是把一类事物的共有属性(字段)和行为(方法)提取出来,形成一个物理模板。这种研究问题的方法称为抽象。

例子:
银行账号都有卡号,密码和余额属性,且可以存款,取款,查询余额这些行为,我们将这些属性和行为提取出来,形成一个模板,用代码实现如下:
项目结构图:

account.go

package model

import "fmt"

// 银行账号
type Account struct {
	AccountNo string  // 卡号
	Pwd       string  // 密码
	Balance   float64 // 余额
}

// 存款
func (account *Account) Deposite(money float64, pwd string) {
	// 检验密码
	if account.Pwd != pwd {
		fmt.Println("密码不正确")
		return
	}

	// 检验存款金额
	if money < 0 {
		fmt.Println("输入的金额不正确")
		return
	}
	account.Balance = account.Balance + money
	fmt.Println("存款成功")
}

// 取款
func (account *Account) WithDraw(money float64, pwd string) {
	// 检验密码
	if account.Pwd != pwd {
		fmt.Println("密码不正确")
		return
	}
	// 检验取款金额
	if money < 0 || money > account.Balance {
		fmt.Println("输入的金额不正确")
		return
	}
	account.Balance = account.Balance - money
	fmt.Println("取款成功")
}

// 查询余额
func (account *Account) Query(pwd string) {
	// 检验密码
	if account.Pwd != pwd {
		fmt.Println("密码不正确")
		return
	}
	fmt.Printf("你的账号%v余额为%v\n", account.AccountNo, account.Balance)
}

main.go

package main

import (
	"model"
)

func main() {
	account := model.Account{
		AccountNo: "pf11111111",
		Pwd:       "666666",
		Balance:   1000,
	}
	// 查询余额
	account.Query("666666")
	// 存款
	account.Deposite(1000, "666666")
	// 查询余额
	account.Query("666666")
	// 取款
	account.WithDraw(200, "666666")
	// 查询余额
	account.Query("666666")
	// 查询余额
	account.Query("888888")
}

输出结果:

你的账号pf11111111余额为1000
存款成功
你的账号pf11111111余额为2000
取款成功
你的账号pf11111111余额为1800
密码不正确

二、 封装

封装就是将抽象出来的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过授权的操作(方法),才能对字段进行操作。现实中对电视机的操作就是典型的封装。

1. 实现封装的步骤
  • 将结构体、字段(属性)的首字母小写(不能导出,其他包不能使用,类似private)
  • 给结构体所在的包提供一个工厂模式的函数,首字母大写,类似一个构造函数
  • 提供一个首字母大写的Set方法,用于对属性判断并赋值
  • 提供一个首字母大写的Get方法用于获取属性的值

例子:
一个人的年龄和工资是属于隐私信息,我们如何实现封装,代码如下:
项目结构图:

person.go

package model

import "fmt"

type person struct {
	Name string  // 名字
	age  int     //年龄 其他包不可用
	sal  float64 //工资 其他包不可用
}

// 写一个工厂模式的函数,相当于构造函数
func NewPerson(name string) *person {
	return &person{
		Name: name,
	}
}

// 为age和sal编写一对SetXxx方法和GetXxx的方法
func (p *person) SetAge(age int) {
	if age > 0 && age < 150 {
		p.age = age
	} else {
		fmt.Println("年龄范围不正确")
	}

}

func (p *person) GetAge() int {
	return p.age
}

func (p *person) SetSal(sal float64) {
	if sal > 0 {
		p.sal = sal
	} else {
		fmt.Println("薪水范围不正确")
	}
}

func (p *person) GetSal() float64 {
	return p.sal
}

main.go

package main

import (
	"fmt"
	"model"
)

func main() {
	p := model.NewPerson("小王")
	fmt.Println("p=", *p)
	p.SetAge(20)
	p.SetSal(5000)
	fmt.Printf("%v年龄是%v,薪水是%v\n", p.Name, p.GetAge(), p.GetSal())
}

输出结果:

p= {小王 0 0}
小王年龄是20,薪水是5000

三、继承

当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。其他结构体不需要重新定义这些属性和方法,只需嵌套该结构体的匿名结构体即可。也就是说在Golang中,如果一个struct嵌套了另一个结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性
例子:
编写一个学生考试系统
项目结构图:

student.go

package model

import "fmt"

type Student struct {
	Name  string
	Age   int
	Score float32
}

func (stu *Student) ShowInfo() {
	fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n", stu.Name, stu.Age, stu.Score)
}

func (stu *Student) SetScore(score float32) {
	stu.Score = score
}

pupil.go

package model

import "fmt"

// 小学生
type Pupil struct {
	Student // 嵌入Student匿名结构体,实现继承
}

func (pupil Pupil) Testing() {
	fmt.Println("小学生正在考试中...")
}

graduate.go

package model

import "fmt"

// 大学生
type Graduate struct {
	Student // 嵌入Student匿名结构体,实现继承
}

func (graduate Graduate) Testing() {
	fmt.Println("大学生正在考试中...")
}

main.go

package main

import (
	"model"
)

func main() {
	pupil := model.Pupil{}
	pupil.Name = "tom"
	pupil.Age = 10
	pupil.Testing()
	pupil.SetScore(80)
	pupil.ShowInfo()

	graduate := model.Graduate{}
	graduate.Name = "mary"
	graduate.Age = 20
	graduate.Testing()
	graduate.SetScore(100)
	graduate.ShowInfo()
}

输出结果:

小学生正在考试中...
学生名=tom 年龄=10 成绩=80
大学生正在考试中...
学生名=mary 年龄=20 成绩=100
1.细节
  • 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。
package main

import (
	"fmt"
)

type A struct {
	Name string
}

func (a *A) SayHello() {
	fmt.Println("A.......SayHello", a.Name)
}

type B struct {
	A
	Name string
}

func (b *B) SayHello() {
	fmt.Println("B.......SayHello", b.Name)
}

func main() {
	b := B{}
	b.Name = "tom"
	b.SayHello()
	b.A.SayHello()
	b.A.Name = "marry"
	b.A.SayHello()
}

输出结果:

B.......SayHello tom
A.......SayHello 
A.......SayHello marry
  • 结构体嵌入两个(或多个)匿名结构体,若两个结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译报错
package main

import (
	"fmt"
)

type A struct {
	Name string
	Age  int
}
type B struct {
	Name  string
	Score float32
}

func (a *A) SayHello() {
	fmt.Println("A.......SayHello", a.Name)
}

func (b *B) SayHello() {
	fmt.Println("B.......SayHello", b.Name)
}

type C struct {
	A
	B
}

func main() {
	var c C
	// 如果C没有Name字段,而A和B有Name字段,这时就必须通过指定匿名结构体名字区分
	// 所以c.Name就会编译错误,这个规则对方法也是一样的
	// c.Name = "tom" // 编译报错
	c.A.Name = "tom"
	c.Age = 10
	c.B.Name = "jack"
	c.Score = 88.8
	// c.SayHello() // 编译报错
	fmt.Println("c=", c)
	c.A.SayHello()
	c.B.SayHello()
}

输出结果:

c= {{tom 10} {jack 88.8}}
A.......SayHello tom
B.......SayHello jack
  • 如果一个结构体嵌套了一个有名结构体,这种模式是组合,如果是组合关系,那么在访问组合结构体的字段或方法时,必须带上结构体名字的
package main

import (
	"fmt"
)

type A struct {
	Name string
	Age  int
}
type B struct {
	a A // 组合关系
}

func (a *A) SayHello() {
	fmt.Println("A.......SayHello", a.Name)
}

func main() {
	var b B
	// 如果一个结构体嵌套了一个有名结构体,这种模式是组合,如果是组合关系,
	// 那么在访问组合结构体的字段或方法时,必须带上结构体名字的
	b.a.Name = "tom"
	b.a.Age = 10
	fmt.Println("b=", b)
	b.a.SayHello()
}

输出结果:

b= {{tom 10}}
A.......SayHello tom
  • 嵌套匿名结构体后,也可以在创建结构体变量(实例时),直接指定各个匿名结构体字段的值
package main

import (
	"fmt"
)

type Goods struct {
	Name  string
	Price float64
}
type Brand struct {
	Name    string
	Address string
}
type TV struct {
	Goods
	Brand
}

func main() {
	tv1 := TV{Goods{"电视机001", 6000.89}, Brand{"海尔", "山东"}}
	// 嵌套匿名结构体后,也可以在创建结构体变量(实例时),直接指定各个匿名结构体字段的值
	tv2 := TV{
		Goods{
			Name:  "电视机002",
			Price: 8000.89,
		},
		Brand{
			Name:    "康佳",
			Address: "青岛",
		},
	}
	fmt.Println("tv1=", tv1)
	fmt.Println("tv2=", tv2)
}

输出结果:
tv1= {{电视机001 6000.89} {海尔 山东}}
tv2= {{电视机002 8000.89} {康佳 青岛}}

Go