(六)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 上海}

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