Golang學習筆記:物件導向(OOP)

最後更新於 2021 年 5 月 23 日

在不同程式語言有不同的OOP實現方式,例如JAVA支持extends也支持interface,但是Go並沒有像C或者JAVA的Class類,它只有C語言的結構體,用結構體和指針等特性完成一個類的功能。

物件導向的基本概念

基本概念必須先知道 類別物件 究竟是什麼?

類別(Class)

類別定義了一個事物的抽象特點,類的定義包含了資料的形式以及對資料的操作

這樣講有點過於抽象,我們可以直接透過下面程式碼來理解:

首先,我們定義一個類別為 汽車,這個類別會包含車的相關屬性,比如 汽車廠牌、汽車型號、汽車重量,而汽車可以發動、熄火、打開油箱,我們將這些行為定義為該類別的方法。

type 汽車 struct {
	廠牌 string //屬性
	型號 string //屬性
	重量 int //屬性
}

func 發動(car 汽車)  { //方法
  //...
}
func 熄火(car 汽車)  { //方法
  //...
}
func 打開油箱(car 汽車)  { //方法
  //...
}

這段程式碼代表我聲明了一個類別 汽車,小說這個類別具備了汽車的特徵以及一些動作。

物件(Object)

物件是類別的實例,在定義了汽車類別之後,我們可以用這個類別來定義物件:

小白 := 汽車{"Toyota", "RAV4", 1610}
小黑 := 汽車{"Porsche", "718 Cayman GT4", 1365}

我們可以讓小白小黑發動 (同理 熄火、打開油箱)

小白.發動()
小黑.發動()

有了類別和物件的概念之後接著就要了解物件導向的三種特性。

物件導向的三大特性

物件導向的三大特性:封裝、繼承、多形

封裝

其實在剛剛物件的地方我們就已經接觸過封裝了。封裝即是將物件裡的資料隱藏起來,只需透過物件本身所提供的介面去取得物件內部屬性或方法,其他物件無法了解此物件的內部細節,我們可以透過 發動() 來使小白這台汽車發動,但是我們並不需要知道他是如何去發動的。

小白.發動() //發動() 定義了汽車具體該通過什麼方法發動,但是小白並不知道它到底是如何發動的。
小黑.發動()

繼承

在某些情況下,一個類別會有它的子類別,例如汽車這個子類別可能會有「跑車」「休旅車」..等,這種情況下「小白」可能就是休旅車的一個實例,而「小黑」則可能為跑車的一個實例。

子類別會繼承父類的屬性和行為,並且也可以加入子類自己的方法

因為 Go語言中該如何去繼承還沒有講到,所以我使用虛擬碼來舉例:

類別 跑車 繼承 汽車
開始
	掀開車頂()
結束

跑車因為繼承了汽車,所以跑車同時也有父類汽車的 廠牌、型號、重量 三個屬性,以及 發動( )、熄火( )、打開油箱( ) 三個方法,同時還有跑車自己的方法 掀開車頂( ) 。

多形

多形是指由繼承而產生的相關的不同的類別,其物件對同一方法會做出不同的動作。

舉例來說,跑車和休旅車都有 掀開敞篷() 這一方法,但是跑車掀開敞篷() 需要三步驟 而 休旅車掀開敞篷() 一步到位 *此處僅為舉例,並不代表現實如此。

類別 跑車
開始
	掀開車頂()
	開始
		三步驟()
	結束
結束

類別 休旅車
開始
	掀開車頂()
	開始
		一步驟()
	結束
結束

Go語言中實現封裝、繼承、多形

封裝

在其他語言的類屬性和實例函數,Go以structfunc實現。

實作一個Hero類,有三個屬性分別為:name、ad、level,並且擁有三個個實例函數 Show() GetName() SetName(),要注意記得在 func 關鍵字與函數名稱之間加上一個 () 在其中告知函數綁定哪個結構體

package main

import "fmt"

type Hero struct {
	name  string
	ad    int
	level int
}

func (my Hero) Show() { //當前方法屬於哪個結構體()
	fmt.Println("Name = ", my.name)
	fmt.Println("Ad = ", my.ad)
	fmt.Println("Level = ", my.level)
}

func (my Hero) GetName() {
	fmt.Println("Name = ", my.name)
}

func (my Hero) SetName(newName string) {
	//my 是調用該方法的物件的一個副本
	my.name = newName
}

func main() {
	Hero := Hero{name: "小明", ad: 100, level: 10}
	Hero.Show()
}

/*
Name =  小明
Ad =  100
Level =  10
*/

如果有嘗試去使用GetName或SetName兩個方法的話,會發現這兩個方法並沒有任何作用。那麼我們應該如何做才會使GetName和SetName有效呢?

當然就是指針啦!

將方法改為:

type Hero struct {
	name  string
	ad    int
	Level int
}

func (my *Hero) Show() {
	fmt.Println("Name = ", my.name)
	fmt.Println("Ad = ", my.ad)
	fmt.Println("Level = ", my.Level)
}

func (my *Hero) GetName() {
	fmt.Println("Name = ", my.name)
}

func (my *Hero) SetName(newName string) {
	my.name = newName
}

func main() {
	Hero := Hero{name: "小明", ad: 100, Level: 10}
	Hero.Show()
	Hero.SetName("老王")
	Hero.Show()
}

/*
Name =  小明
Ad =  100
Level =  10
Name =  老王
Ad =  100
Level =  10
*/

繼承

父類

定義

使用 typestruct 來定義一個類別

type Human struct {
	name string
	sex string
}
方法的實現

我們可以透過在 func 關鍵字與方法名稱之間加上 (arg 父類) 來定義類別方法:

func (arg Human) Eat() { 
	fmt.Println("Human.Eat()...")
}

這裡的 arg 僅是調用該方法的物件的一個副本,取什麼都行。

子類

繼承父類

在子類中直接輸入父類的名稱即可引入父類的所有屬性和方法,並且我們可以重定義父類方法和加入子類的新方法:

type superMan struct {
	Human //superMan 繼承了Human類的方法
	level int
}

//重定義父類方法
func (my superMan) Eat(){
	fmt.Println("superMan.Eat()...")
}

//子類的新方法
func (my superMan) Fly(){
	fmt.Println("superMan.Fly()...")
}
定義子類
  • 宣告時直接初始化值
s := superMan{ Human{"小明","男"}, 100}
  • 先宣告再賦值
var s superMan
s.name = "小王"
s.sex = "男"
s.level = 55

多形

在Go語言中我們透過實作 interface 來實現多形,interface本質是一個指針,多形就是父類指針指向子類。

多形基本要素

1.有一個父類(有interface)
2.有子類(實現了父類的全部interface)
3.父類型別的變數(指針)指向(引用)子類的具體變數

使用 type name interface 定義一個 AnimalIF 介面:

//定義一個interface
type AnimalIF interface {
	Sleep()
	GetColor() string //獲取動物的顏色
	GetType() string //獲取動物的種類
}

再使用 struct 定義 Cat 和 Dog 類,這兩個類底下還有Sleep( )、GetColor( )、GetType( )三個方法:

//具體的類
type Cat struct {
	color string //貓的顏色
}

//重寫接口方法
func (my *Cat) Sleep() {
	fmt.Println("Cat is sleep")
}

func (my *Cat) GetColor() string {
	return my.color
}

func (my *Cat) GetType() string {
	return "Cat"
}

//具體的類
type Dog struct {
	color string
}

//重寫接口方法
func (my *Dog) Sleep() {
	fmt.Println("Dog is sleep")
}

func (my *Dog) GetColor() string {
	return my.color
}

func (my *Dog) GetType() string {
	return "Dog"
}

然後我們可以通過宣告一個interface型別的變數,將 AnimalIF 指向 Cat 和 Dog,便能透過接口實現具體的類:

func main() {
	var animal AnimalIF //interface型別 , 父類指針
	animal = &Cat{"Brown"}
	animal.Sleep() // 調用的就是 Cat 的 Sleep() 方法 , Cat is sleep

	animal = &Dog{"Yellow"}
	animal.Sleep() //Dog is sleep
}

透過 interface 來實現多形

func showAnimal(animal AnimalIF) {
	animal.Sleep() //多形
	fmt.Println("color = ",animal.GetColor())
	fmt.Println("kind = ",animal.GetType())
}

func main() {
	cat := Cat{"Brown"}
	dog := Dog{"Yellow"}
	showAnimal(&cat)
	showAnimal(&dog)
}

通用萬能型別

可以用空介面 interface { } 型別引用任意的資料型別。

舉例來說:

func myFunc(arg interface{}) {
	fmt.Println("myFunc is called...")
	fmt.Println(arg)	
}

type Book struct {
	auth string
}

func main() {
	book := Book{"Golang"}
	myFunc(book)
	myFunc(100)
	myFunc("abc")
	myFunc(3.14)
}

/*
myFunc is called...
{Golang}
myFunc is called...
100
myFunc is called...
abc
myFunc is called...
3.14
*/

由此可知空介面為萬能型別

型別斷言

那麼空介面該如何區分此時引用的資料型別到底是什麼?

func myFunc( arg interface{} ) {
	fmt.Println("myFunc is called...")
	fmt.Println(arg)	
    //給 interface{} 提供 "型別斷言" 的機制
    value,ok := arg.(string) //必須得是空介面才行 , 返回value,ok
    if !ok {
	    fmt.Println("arg is not string type")
    }else{
	    fmt.Println("arg is string type , value = ",value)
	    fmt.Printf("value type is %T\n",value)
    }
}

完整例子:

func myFunc(arg interface{}) {
	fmt.Println("myFunc is called...")
	fmt.Println(arg)	

	value,ok := arg.(string) //必須得是空接口才行 , 返回value,ok
	if !ok {
		fmt.Println("arg is not string type")
	}else{
		fmt.Println("arg is string type , value = ",value)
		fmt.Printf("value type is %T\n",value)
	}
}

type Book struct {
	auth string
}

func main() {
	book := Book{"Golang"}
	myFunc(book)
	myFunc(100)
	myFunc("abc")
	myFunc(3.14)
}

/*
myFunc is called...
{Golang}
arg is not string type
myFunc is called...
100
arg is not string type
myFunc is called...
abc
arg is string type , value =  abc
value type is string
myFunc is called...
3.14
arg is not string type
*/
4e52d54f6bc42abb41d26eb5b0df6517?s=250&d=wavatar&r=g Golang學習筆記:物件導向(OOP)
0 0 評分數
Article Rating
訂閱
通知
guest
0 Comments
在線反饋
查看所有評論