(六)Go语言学习笔记 - 面向对象封装、继承

1 封装

封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作方法,才能对字段进行操作。
封装好处:

  • (1)隐藏实现细节
  • (2)可以对数据进行验证,保证安全合理

如何体现封装

  • (1)对结构体中的属性进行封装
  • (2)通过 方法 来实现封装

封装实现步骤

  • (1)将结构体、字段(属性)的首字母小写(不能导出,其它包不能使用,类似private)
  • (2)给结构体所在的包提供一个工厂模式的函数,首字母大写。蕾西一个构造函数
  • (3)提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值
	func (var 结构体类型名) SetXxx(参数列表) (返回值列表) {
		// 加入数据验证的业务逻辑
		var.字段 = 参数
	}
  • (4)提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值
	func (var 结构体类型名) GetXxx(){
		// 返回值即可
		return var.字段
	}

在Golang开发中并没有强调封装,Golang本身对面向对象的特性做了简化。

1.1 实例1

自定义包

package model

import "fmt"

type person struct {
	Name string
	age int
	sal float64
}

// 设置一个工厂函数
func NewPerson(name string) *person {
	return &person{
		Name : name,
	}
}

// 创建一个设置年龄的方法
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 > 3000 && sal < 30000 {
		p.sal = sal
	} else {
		fmt.Println("您输入的薪水有误")
	}
}

// 获取薪水
func (p *person) GetSal() float64 {
	return p.sal
}

引用自定义包

package main

import (
	"fmt"
	"go_code/OOPobject/encapsulation/model"
)

func main()  {

	p := model.NewPerson("Smith")

	p.SetAge(19)
	p.SetSal(6000)

	fmt.Println(p)
	fmt.Println("名字=", p.Name, "年龄=", p.GetAge(), "薪水=", p.GetSal())
	//&{Smith 19 6000}
	//名字= Smith 年龄= 19 薪水= 6000

}

1.2 实例2

要求:

  • (1)Account结构体字段:账号(长度在6~10之间)、余额(必须>20)、密码(必须是六位)
  • (2)通过SetXxx和GetXxx方法给字段赋值
  • (3)main函数中测试

自定义包

package model

import (
	"fmt"
)

// 在model包中定义Account结构体
type account struct {
	accountNo string
	pwd string
	balance float64
}

func NewAccount(accountNo string, pwd string, balance float64) *account {
	if len(accountNo) < 6 || len(accountNo) > 10 {
		fmt.Println("账号长度有误...")
		return nil
	}

	if len(pwd) != 6 {
		fmt.Println("密码长度有误...")
		return nil
	}

	if balance < 20 {
		fmt.Println("余额不足...")
		return nil
	}
	return &account{
		accountNo: accountNo,
		pwd: pwd,
		balance: balance,
	}
}

func (a *account) SetPwd(pwd string) {
	if len(pwd) != 6 {
		fmt.Println("密码长度有误...")
		return
	}
	a.pwd = pwd
}

func (a *account) GetPwd() string {
	return a.pwd
}

引用自定义包

package main

import (
	"fmt"
	"go_code/OOPobject/encapsulateprise/model"
)

func main()  {
	a := model.NewAccount("建设银", "123456", 40)

	a.SetPwd("123456")
	fmt.Println(a.GetPwd())
}

2 继承

  • 继承可以解决代码复用,让变成更加靠近人类的思维

  • 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法。

  • 其它的结构体不需要重新定义这些属性和方法,只需要嵌套一个匿名结构体即可。

  • 在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。

继承的语法

type Goods struct{
    Name string
    Price int
}

type Book struct{
    Goods // 这里就是嵌套匿名结构体Goods
    Writer string
}

// 使用Goods中的属性最简单的形如 Book.Goods.Name

2.1 实例1

比如有一个学生管理系统,传统情况下需要定义许多个不同年级的结构体来区分不同年级学生的姓名成绩及做的事情等,这样用继承就可以声明一个Student的匿名结构体,被不同年级的结构体所嵌套使用

package main

import (
	"fmt"
)

// 定义一个Student匿名函数,供不通年级的struct嵌套使用
type Student struct{
	Name string
	Age int
	Score float64
}
// 定义一个Student的方法SetScore
func (stu *Student) SetScore(score float64){
	stu.Score = score
}
// 定义一个Student的方法ShowInfo
func (stu *Student) ShowInfo()  {
	fmt.Printf("学生姓名=%v 年龄=%v 成绩=%v \n", stu.Name, stu.Age, stu.Score)
}

// 定义大学生的结构体
type Graduate struct{
	Student
}

// 定义大学生的方法
func (p *Graduate) testing(){
	fmt.Println("大学生正在考试中...")
}

// 定义小学生的结构体
type Pupil struct{
	Student
}

// 定义小学生的方法
func (p *Pupil) testing(){
	fmt.Println("小学生正在考试中...")
}

func main()  {
	// 定义一个graduate变量
	graduate := &Graduate{}
	graduate.Student.Name = "tom"
	graduate.Student.Age = 18
	graduate.SetScore(70)
	graduate.testing()
	graduate.ShowInfo()


	// 定义一个pupil变量
	pupil := &Pupil{}
	pupil.Student.Name = "Marry"
	pupil.Student.Age = 20
	pupil.SetScore(80)
	pupil.testing()
	pupil.ShowInfo()

	/* 输出信息如下:
	大学生正在考试中...
	学生姓名=tom 年龄=18 成绩=70
	小学生正在考试中...
	学生姓名=Marry 年龄=20 成绩=80
	*/
	 */
}
// 代码的扩展性和维护性提高了
// 代码的复用性提高了

2.2 details

  • (1)结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用
  • (2)匿名结构体字段访问可以简化
	pupil.Student.Name = "Marry"  // ==> pupil.Name = "Marry"
	pupil.Student.Age = 20		  // ==> pupil.Age = 20
  • (3)当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则,如果希望访问匿名结构体的字段和方法,可以通过指定具体匿名结构体名来区分
package main

import (
	"fmt"
)

// 当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则,
// 如果希望访问匿名结构体的字段和方法,可以通过指定具体匿名结构体名来区分
type A struct {
	Name string
	Age int
}

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

// 定义一个B结构体,嵌套A结构体
type B struct {
	A
	Name string
}

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

func main()  {
	b := B{}
	b.Name = "jack"
	b.Age = 20
	b.SayHello()  // B SayHello =  jack
	b.A.SayHello()  //A SayHello =   会输出空串

}
  • (4)结构体嵌入两个或多个匿名结构体,如果两个匿名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法),在访问时,就必须明确指定匿名结构体名字,否则编译会报错
// 下边三个结构体,C结构体引用A和B结构体,此时若C指定Name但是没有明确嵌套的哪个结构体,那么就会报错
type A struct{
	Name string
	Age int
}

type B struct{
	Name string
	Scores float64
}

type C struct{
	A
	B
}
  • (5)如果一个struct嵌套了一个有名的结构体,这种模式就是组合,如果是组合关系,那么再访问组合的结构体的字段或方法时,必须带上结构体的名字
// 形如:
type A struct{
    Name string
    Age int
}

type C struct{
   a A  // 组合关系
}
  • (6)嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值
package main

import (
	"fmt"
)

type Goods struct{
	Name string
	Price float64
}

type Brand struct{
	Name string
	Address string
}

type Tv struct{
	Goods
	Brand
}

// 用指针的形式来指定嵌入结构体
type Tv2 struct{
	*Goods
	*Brand
}

func main() {
	// tv := Tv{ Goods{"电视机", 1000}, Brand{"海尔", "北京"} }
	tv := Tv{
		Goods{
			Name : "电视机",
			Price : 1000,
		},
		Brand{
			Name : "海尔",
			Address : "北京",
		},
	}
	fmt.Println("tv = ", tv)  // tv =  {{电视机 1000} {海尔 北京}}

	tv2 := Tv2{
		&Goods{
			Name : "电视机003",
			Price : 2000,
		},
		&Brand{
			Name : "TCL",
			Address : "上海",
		},
	}
	fmt.Println("tv = ", *tv2.Goods, *tv2.Brand)  // tv =  {电视机003 2000} {TCL 上海}

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章