2023/04/05 更新: 為了避免本文章散落在不同網站,之後統一由部落格更新,再麻煩從部落格查看~
在 DAY 1 ~ DAY 12 已經介紹了我認知常見的 concurrency patterns,接下來就要介紹經典的 GoF design pattern,但在這之前我認為可以先介紹一些 design pattern 的基本概念供大家有相同的認知,以便更清楚的講解,所以接下來幾篇會說明這些概念。
本篇要介紹的是 UML Class diagrams!
透過圖形來說明物件建立、耦合、互動的關係
GoF design pattern 主要在講解物件與物件的關係,如果直接把 code 貼出來,「一行一行」的程式碼很難讓剛接觸此系統的人有「整體」的想像,俗話說「一張圖,勝過千言萬語」,Class diagrams 就是希望就由圖來讓人更快速的對此系統有初步的理解,所以應用在講解 GoF design pattern 也是合適的。
但 Class diagrams 的定義沒有一個統一標準,有許多規則的演變,但並不是每種都很常使用,我選擇以下幾種我認為最重要的規則,並且用mermaid-js繪畫(使用教學可以看我這篇讓你心裡的邏輯具現化的念能力工具),實用與讓人看得懂最為重要,如果規則太複雜讓人無法理解就適得其反了。
先說明幾個小細節:
(相關的 code 在Github - go-design-patterns)
Generalization/Inheritance 繼承
A 會先定義好相關的屬性與方法並且實作,而 B 繼承 A 後,B 會擁有 A 相關的屬性與方法,並且 B 也可有有自己的屬性與方法
舉例來說:PS5 是繼承自 PS4 而誕生的,PS5 能擁有玩 PS4 遊戲的功能,但也可以玩 PS5 的遊戲
code 如下,需特別注意的是其實 golang 沒有繼承,只有組合(下方會介紹),但組合只組一項事物,也可以達到繼承的效果:
package main
import "fmt"
type PS4 struct {
PS4Game string
}
func (p PS4) PlayPS4Game() {
fmt.Printf("play %s", p.PS4Game)
}
type PS5 struct {
PS4
PS5Game string
}
func (p PS5) PlayPS5Game() {
fmt.Printf("play %s", p.PS5Game)
}
func main() {
ps5 := PS5{
PS4: PS4{PS4Game: "KOF14"},
}
ps5.PlayPS4Game()
}
Realization/Implementation 實作
A 會先定義好相關的方法但不實作,而 B 必須以 A 的定義來實作相關方法
舉例來說:表演比賽說明來參賽的人只要會唱歌即可,所以不同的人種都可以參加
code 如下:
package main
import "fmt"
type Contestant interface {
Sing()
}
type Mike struct{}
func (m Mike) Sing() { fmt.Println("mike singing") }
type Kevin struct{}
func (m Kevin) Sing() { fmt.Println("kevin singing") }
type York struct{}
func (m York) Sing() { fmt.Println("york singing") }
func Show(contestant Contestant) {
contestant.Sing()
}
func main() {
Show(Mike{})
Show(Kevin{})
Show(York{})
}
Composition 組合
A 擁有多個部件,並且這些部件與 A 的生命週期是相同的,他們彼此強制聯繫
舉例來說:PS5 擁有搖桿、CPU、顯示晶片這些部件,這些部件是為了 PS5 而生
code 如下:
package main
import "fmt"
type PS5 struct {
CPU
Controller
GPU
}
type CPU struct{}
type Controller struct{}
type GPU struct{}
func main() {
ps5 := PS5{
CPU{},
Controller{},
GPU{},
}
fmt.Println(ps5)
}
Aggregation 聚合
A 擁有多個部件,並且這些部件與 A 的生命週期是不相同的,他們彼此不強制聯繫
跟組合類似,但關鍵差異是:
舉例來說:switch 可以買鼓組也可以買專用手把來玩太鼓達人,但不買也是可以玩的
code 如下:
package main
import "fmt"
type NintendoSwitch struct {
Controller string
}
func (n *NintendoSwitch) SetController(controller string) {
n.Controller = controller
}
func (n NintendoSwitch) PlayGame() {
if n.Controller != "" {
fmt.Printf("use %s to play game", n.Controller)
} else {
fmt.Println("use default controller to play game")
}
}
func main() {
nintendoSwitch := NintendoSwitch{}
nintendoSwitch.SetController("drum")
nintendoSwitch.PlayGame()
}
Association 關聯
A 的屬性中擁有 B,並在 A 創建的時候帶入 B
跟聚合相似,但關鍵差異是:
.SetXXX()
這類的 function 將屬性設置但 golang 並不能再創建物件時就將屬性設置,因為 golang 沒有 construct,所以我們可透過一個CreateXXX()
來模擬
舉例來說:我擁有 mac,我可以利用 mac 來去處理鐵人賽文章
code 如下:
package main
import "fmt"
type Me struct {
Mac
}
type Mac struct{}
func (m Mac) WriteArticle() {
fmt.Println("write article")
}
func CreateMe(mac Mac) *Me {
return &Me{mac}
}
func (m Me) WriteIronmanArticle() {
m.Mac.WriteArticle()
}
func main() {
me := CreateMe(Mac{})
me.WriteIronmanArticle()
}
Dependency 相依
A 不擁有屬性 B,但 A 的某方法有透過參數 B 來實現
舉例來說:我雖然不擁有公司白板,但我可以透過它來跟其他人溝通
code 如下:
package main
import "fmt"
type Me struct{}
type Whiteboard struct{}
func (w Whiteboard) DrawOnWhiteboard() {
fmt.Println("write")
}
func (m Me) Discuss(whiteboard Whiteboard) {
whiteboard.DrawOnWhiteboard()
}
func main() {
me := Me{}
me.Discuss(Whiteboard{})
}