# Day 36 : Golang - Go的物件導向設計
## 前言
在說明完Struct和Interface,以及Python和JaavaScript如何實現物件導向,這次用Go的角度看物件導向。
[官方文件](https://go.dev/doc/faq#Is_Go_an_object-oriented_language):
> Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy.
其答案非常模糊,自己也沒辦法斷定,但是不是也可思考成答案不是這麼重要?
Go沒有class存在,沒有class階層,沒有物件的概念,一般來說不會說是物件導向的程式語言,但確實是可以用不同方法來實現物件導向。那接下來就看看Go可以如何展現OOP的特性吧。
## Struct is similar to Class
在Go如果有一組資料總是常常會需要到,我們會使用struct來自行定義,而如果需要一個函式專門用來處裡某個struct,可以定義一個receiver function。
就像我們定義一個Pet型別的sturct,透過指定reciver為一個Pet類別的變數p,建立一個myInfo()的函式,當作Pet的方法。這時候在第21行使用`p.myInfo()`的動作稱為"selector",即它為Pet型別的receiver p選擇一個適當的方法。
```go=
package main
import (
"fmt"
)
type Pet struct {
petType string
breed string
}
func (p Pet) myInfo() string{
return fmt.Sprintf("I am a %s and my breed is %s",p.petType,p.breed)
}
func main() {
p :=Pet{
petType:"dog",
breed:"Golden Retriever",
}
fmt.Println(p.myInfo())
}
```
## 繼承
在Go裡面透過composition來達成繼承的效果,composition就是透過將一個或多個物件放到另一個物件裡面,以Go來看就是建立一個struct型別再放入另一個struct 型別,此方法又稱為型別內嵌(type embedding)。
例如我新增一個Dog的結構體然後內嵌Pet結構體,其名稱和型別都是Pet。
然後於第17行初始化空的結構體,再透過`.`增加欄位的值(當然也可以初始化的時候就直接加進去),可以看到dog也可以使用Pet的屬性和方法了。
```go=
type Pet struct {
petType string
breed string
}
type Dog struct{
Pet
age int
name string
}
func (p Pet) myInfo() string{
return fmt.Sprintf("I am a %s and my breed is %s",p.petType,p.breed)
}
func main() {
dog:=Dog{}
dog.age=7
dog.name="Brown"
dog.Pet = Pet{
petType: "dog",
breed: "Golden Retriever",
}
fmt.Println(dog.myInfo())
// I am a dog and my breed is Golden Retriever
fmt.Println(dog.name)
// Brown
fmt.Println(dog.breed)
// Golden Retriever
}
```
## 封裝
一個變數的屬性或是方法無法被使用就是被封裝(encapsulated),在Go只有一個機制,就是透過變數第一個字是否大寫來判斷,如為大寫可以被其他package使用,小寫則不行。
這邊使用在[Day 22 : Golang Struct(2)](https://hackmd.io/gLY_tf7MR5ycUDy_Spqn2A)說明的例子
當User結構體有三個fields:id,lastname,contact,其中contact是一個內嵌的結構體。print()函式為一個屬於User結構使用的方法,可以印出該型別變數的資訊,並建立兩個檔案,user/user.go、main.go。
在user.go定義了公開的User結構和私有的field和公共的方法Print()、UpdateName()。
```go!
// user/user.go
package user
import "fmt"
type User struct {
id int
lastname string
contact ContactInfo
}
type ContactInfo struct {
email string
zipCode int
}
func NewUser(id int, lastname string, email string, zipCode int) *User {
contact := ContactInfo{email, zipCode}
return &User{id, lastname, contact}
}
func (u *User) Print() {
fmt.Printf("%v\n", u)
}
func (u *User) UpdateName(lastname string) {
u.lastname = lastname
}
```
在main.go裡面先透過NewUser()函式建立一個新的User物件,並取得該物件的指標。之後就可以透過指標使用物件的方法,但在第11行英文`lastaname`是私有的,所以無法存取會發生錯誤,而Print()和UpdateName()都是公開的方法所以可已執行。
```go=
//main.go
package main
import (
"playground/user"
)
func main() {
dylan:= user.NewUser(123,"Huang","dylan123@gmail.com",456)
// fmt.Println(dylan.lastname)
dylan.Print() // &{123 Huang {dylan123@gmail.com 456}}
dylan.UpdateName("Wu")
dylan.Print() // &{123 Wu {dylan123@gmail.com 456}}
}
```
## 多型
當不同變數呼叫同一個方法可以得到不同結果就是多型,最簡單的例子如下,透過實體化兩個不同的狗狗,呼叫myInfo()會得到不同的結果。
```go=
type Pet struct {
petType string
breed string
}
type Dog struct{
Pet
age int
name string
}
func (p Pet) myInfo() string{
return fmt.Sprintf("I am a %s and my breed is %s",p.petType,p.breed)
}
func main() {
dog1 :=Dog{
Pet: Pet{
petType:"dog",
breed:"Golden Retriever",
},
age:7,
name:"Brown",
}
dog2 :=Dog{
Pet: Pet{
petType:"dog",
breed:"Samoyed",
},
age:1,
name:"White",
}
fmt.Println(dog2.myInfo()) // I am a dog and my breed is Samoyed
fmt.Println(dog1.myInfo()) // I am a dog and my breed is Golden Retriever
}
```
但這樣不就太普通了? 記得之前花很多時間學習的interface嗎,在Go裡面多型就是透過interface來執行,最經典的例子就是io package和fmt package,想想為什麼`fmt.Println()`可以接受任何型別的變數呢,因為在此函數裡面參數的型別是一個空介面,任何變數都會實現空介面,所以都可以正常輸出,這就是一個多型的概念,在此當然也要修改一下狗狗這個例子。
先定義一個Pet介面,裡面有myInfo()這個方法。
接著定義一個Dog結構體,且該Dog型別也有個專屬的函式myInfo(),所以可以說明Dog型別實現了Pet介面,所以它可以被賦值给Pet接口型別的變數 pet1,這時候pet1就可以使用Dog結構體中實現的myInfo()。
```go!
package main
import (
"fmt"
)
type Pet interface {
myInfo() string
}
type Dog struct{
petType string
breed string
age int
name string
}
func (d Dog) myInfo() string{
return fmt.Sprintf("I am a %s and my breed is %s", d.petType, d.breed)
}
func main() {
dog1 := Dog{
petType:"dog",
breed:"Golden Retriever",
age:7,
name:"Brown",
}
dog2 := Dog{
petType:"dog",
breed:"Samoyed",
age:1,
name:"White",
}
var pet1 Pet
pet1 = dog1
fmt.Println(pet1.myInfo())
// I am a dog and my breed is Golden Retriever
var pet2 Pet
pet2 = dog2
fmt.Println(pet2.myInfo())
// I am a dog and my breed is Samoyed
}
```
## References
1. [從Go語言看物件導向](https://www.ithome.com.tw/voice/107839)
2. [Introduction to Object-oriented Programming in Go](https://www.developer.com/languages/oop-go/)
3. [Go是一门面向对象编程语言吗](https://tonybai.com/2023/03/12/is-go-object-oriented/)
###### tags: `About Go`