【Go】利用 struct 優化(一)

Synopsis

  1. 透過 struct 方式,容易看懂程式
  2. 透過 pointer 方式,降低 memory 的使用 (雖然在程式結束後,會自動回收用不到的 memory)
  3. 使用 func as method 方式,讓程式碼在 func 使用上更容易被理解

Example

一般來說,如果要用程式碼計算『圓形面積』、『長方形面積』,可以用一些簡單的方式攥寫

但回頭看這個程式碼,會發現

  • 如果要追蹤『rx1, ry1, rx2, ry2』或者『cx, cy, cr』當程式碼一長 or 複雜,就很難理解這個參數的意義
  • 儘管有寫註解,在 function 中,一樣很難快速理解

解決方案 : 使用 struct 來幫助理解

use struct

struct 是一種 type, 它包含了名稱的欄位, 例如:可以把 Rectangle 寫成如下方式

1
2
3
4
5
6
7
8
9
type Coordinate struct {
	x float64
	y float64
}

type Rectangle struct {
	start Coordinate
	end   Coordinate
}
  • 新增一個 Coordinate 的物件, 類型(type)是 struct, 裡面夾帶 (x,y) 欄位
  • 新增一個 Rectangle, 類型(type)是 struct 的物件, 裡面夾帶 ‘起始座標’ 與 ‘結束座標’

宣告方法可以有以下幾種

  1. 以下都相同,跟 (2) 不同的是,返回是一個指標
    • r := new(Rectangle), 而 new 的主要作用是為 type 申請 memory,並返回指向 memory 的指標
    • r := &Rectangle{}
  2. 以下也都相同
    • var r Rectangle
    • r := Rectangle{}
    • r := Rectangle{Coordinate{}, Coordinate{}}
    • r := Rectangle{ start: Coordinate{}, end: Coordinate{},}

優化開始

  • steps
    1. 將 func() 參數物件改為傳遞 struct
    2. 將 func() 中 strct 設定為指標
    3. function as a method

1. 將 func() 參數物件改為傳遞 struct

從上面的基本操作,建立了 Rectangle 物件, type 是 struct 所以將 rectangleArea() 與 distance() 變更為

1
2
3
4
5
func rectangleArea(r Rectangle) float64 {
	l := r.end.x - r.start.x
	w := r.end.y - r.start.y
	return l * w
}

在 main 中, 可以改為, 如此可以輕鬆地理解 r 來自於 Rectangle 這個結構中的欄位

1
2
3
4
5
6
7
8
9
10
11
r := Rectangle{
	start: Coordinate{
		x: 1,
		y: 1,
	},
	end: Coordinate{
		x: 10,
		y: 10,
	},
}
fmt.Println(rectangleArea(r))

2. 將 func() 中 strct 設定為指標

以上 func 中, 重新宣告了一個 Rectangle 的物件 可以使用 fmt.Printf("%p\n", ThePointer) 查看記憶體位置的變化

for example

0xc00009c000 <= main 中的 Rectangle
0xc00009c020 <= func 中的 Rectangle

但可以用 pointer, 避免重複宣告並佔用 memory, 浪費資源

1
2
3
4
5
6
func rectangleArea(r *Rectangle) float64 {
    fmt.Printf("%p", r) // 因為這邊的 r 已經是 pointer
	l := r.end.x - r.start.x
	w := r.end.y - r.start.y
	return l * w
}

在 main 中

1
2
3
4
5
6
7
8
9
10
11
12
r := Rectangle{
	start: Coordinate{
		x: 1,
		y: 1,
	},
	end: Coordinate{
		x: 10,
		y: 10,
	},
}
fmt.Printf("%p\n", &r)
fmt.Println(rectangleArea(&r))

3. function as a method

解決了以上的問題,會發現

  1. rectangle 與 circle 都有 area, 雖然可以在命名上作區別, 但有更好的方法 且可以透過 . 的方式, 快速地在編輯器上引用此 method
  2. 在 main 中,帶入的物件是 &r 會有點難以理解

將 rectangleArea() 改為 rectangle 的 method

1
2
3
4
5
6
func (r *Rectangle) area() float64 {
	fmt.Printf("%p\n", r)
	l := r.end.x - r.start.x
	w := r.end.y - r.start.y
	return l * w
}

在 main 中

1
fmt.Println(r.area())

優化結束

這是一個簡短的例子,當我們將功能越寫越多時,程式碼會越複雜 我們可以透過此方法達成以下三個目的

  1. 透過 struct 方式,容易看懂程式
  2. 透過 pointer 方式,降低 memory 的使用 (雖然在程式結束後,會自動回收用不到的 memory)
  3. 使用 func as method 方式,讓程式碼在 func 使用上更容易被理解

結論

  1. 過於複雜的參數傳遞,雖然我們可以用命名方式來釐清,但名稱越長,也會使程式碼難以理解
  2. 優化並非將程式碼縮短(但的確在大型架構下,可以縮短,並更容易理解)

問題

一個人裡有各種資訊物件,身分證、健保卡、護照,都用在不同的地方,也都有各自的 ID 可以透過 struct 將 『身分證、健保卡、護照』 各自建立起來,用在不同的目的上 但是否有方法整合這些物件?

參考下篇: struct優化(二):interface