老實說今天 Hackerrank 的主題讓我有點不想浪費一天的篇幅去講,因為是在講 Nested Logic,說穿了就只是要你練習兩層以上的 If Else 之類的東西,所以我相信應該不需要特別再去說明這個主題。不然今天就讓我們來聊聊先前一直想要探討的:為何在 Golang 或 Rust 會提倡 "以組合代替繼承" 吧!
is a
,後者則是 has a
的關係。一般來說,使用組合的比起繼承更加彈性以及不易有 Bug。但這並不表示繼承不好,而是說很多時候使用組合的方式可能會是更好的做法。當我們如果是使用繼承的話,必須要對父類別的實作去了解,這樣才知道我們需不需要在子類別去重寫,屬於 Whitebox Reuse。然而組合則是 Blackbox Reuse,表示不需要去了解 Object 的實作,而是把 Object 當作是 Blackbox 的方式去操作,只要我們替換了 Object 就能夠改變實際的行為方式。Car
),並定義 accelerate
這個方法,而不同品牌的車 (CarA
, CarB
...)則分別宣告子類別並繼承 Car
,並將 accelerate
做 Override。但注意到在這裡每一種車的加速行為都是在編譯時期就已經決定而無法更改,例如說我們沒辦法將 CarB
的煞車行為在執行期時去做更換。接著讓我們看看如果是用組合的思維呢?首先我們先定義一個 Inrerface,其定義了加速的行為,例如 AccelerateBehavior
,而不同的加速方式則是定義不同的類別去實作這個 Interface,例如 AccelerateAMethod
、AccelerateBMethod
。接著有趣的來了,我們再也不用定義什麼 CarA
或 CarB
,我們只要有一個類別 Car
,而 Car
有個屬性 accelerateBehavior
類型是 AccelerateBehavior
,我們可以在創造 Car
的時候給予這個屬性不同的實作類別 (例如 AccelerateAMethod
或 AccelerateBMethod
),藉此將加速行為組裝進 Car
之中囉!因此根本來說,我們關注的是用繼承重寫還是給予不同的行為實例。package main
import (
"fmt"
)
type AccelerateBehavior interface {
Accelerate(speed int)
}
type AccelerateAMethod struct {
}
func (*AccelerateAMethod) Accelerate(speed int) {
fmt.Println("Accelerate to " + string(speed) + "km by A")
}