目前學習golang的主要需求是為了看懂TiDB的源碼,下麵我們複習一下簡易工廠模式的思想 工廠類型分為三種,創建型模式,結構型模式,行為型模式。 簡單工廠 使用場景:考慮一個簡單的API設計,一個模塊可以提供不同的APi,這些Api都源自同一個基類,不過在繼承基類後不同的子類修改了部分屬性,從而可 ...
目前學習golang的主要需求是為了看懂TiDB的源碼,下麵我們複習一下簡易工廠模式的思想
工廠類型分為三種,創建型模式,結構型模式,行為型模式。
簡單工廠
使用場景:考慮一個簡單的API設計,一個模塊可以提供不同的APi,這些Api都源自同一個基類,不過在繼承基類後不同的子類修改了部分屬性,從而可以產生不同的功能實現,如果希望在使用這些Api時,只需要知道表示該api的一個參數,並提供一個調用方便的方法,把該參數傳入方法即可返回一個相應的按鈕對象,此時,就可以使用簡單工廠模式。
定義
簡單工廠模式(Simple Factory Pattern):又稱為靜態工廠方法(Static Factory Method)模式,它屬於類創建型模式。在簡單工廠模式中,可以根據參數的不同返回不同類的實例。簡單工廠模式專門定義一個類來負責創建其他類的實例,被創建的實例通常都具有共同的父類。
組成角色
Factory:工廠角色
工廠角色負責實現創建所有實例的內部邏輯
Product:抽象產品角色
抽象產品角色是所創建的所有對象的父類,負責描述所有實例所共有的公共介面
ConcreteProduct:具體產品角色
具體產品角色是創建目標,所有創建的對象都充當這個角色的某個具體類的實例。
從時序圖來看,我們可以考慮用構造函數在初始化實例的時候進行重載,但是golang並沒有構造函數。所以我們採用介面實現的方式,先構造一個介面類型的方法作為重載方法,根據傳入的值進行決定需要使用哪一個方法。
package designmode
import "fmt"
type API interface {
Say(name string) string
}
func NewAPI( t int)API {
if t ==1{
return &hiAPI{}
}else if t == 2 {
return &helloAPI{}
}
return nil
}
type hiAPI struct {
say string
}
func (*hiAPI) Say(name string) string {
return fmt.Sprintf("Hi, %s", name)
}
type helloAPI struct {
}
//Say hello to name
func (*helloAPI) Say(name string) string {
return fmt.Sprintf("Hello, %s", name)
}
為什麼需要使用指針傳遞?
我們做一個簡單的實驗,分別列印變數和記憶體地址
- 值傳遞
package learn
import "fmt"
type WindWheel struct {
Age int
Name string
}
func passV(w WindWheel) {
w.Age++
w.Name = "Greact"+w.Name
fmt.Printf("傳入修改後的windWheel:\t %+v, \t記憶體地址;%p\n",w,&w)
}
func main() {
parrot :=WindWheel{Age: 1,Name: "Blue"}
fmt.Printf("原始的windWheel:\t\t %+v, \t\t記憶體地址: %p\n",parrot,&parrot)
passV(parrot)
fmt.Printf("調用後原始的windWheel:\t %+v, \t\t記憶體地址: %p\n",parrot,&parrot)
}
列印結果:
原始的windWheel: {Age:1 Name:Blue}, 記憶體地址:0xc420012260
傳入修改後的windWheel: {Age:2 Name:GreatBlue}, 記憶體地址:0xc4200122c0
調用後原始的windWheel: {Age:1 Name:Blue}, 記憶體地址:0xc420012260
引用傳遞
package learn
import "fmt"
type WindWheel struct {
Age int
Name string
}
func passP(w *WindWheel) {
w.Age++
w.Name = "Great" + w.Name
fmt.Printf("傳入修改後的WindWheel:\t %+v, \t記憶體地址:%p, 指針的記憶體地址: %p\n", *w, w, &w)
}
func main() {
parrot := &WindWheel{Age: 1, Name: "Blue"}
fmt.Printf("原始的WindWheel:\t\t %+v, \t\t記憶體地址:%p, 指針的記憶體地址: %p\n", *parrot, parrot, &parrot)
passP(parrot)
fmt.Printf("調用後原始的WindWheel:\t %+v, \t記憶體地址:%p, 指針的記憶體地址: %p\n", *parrot, parrot, &parrot)
}
列印結果
原始的WindWheel: {Age:1 Name:Blue}, 記憶體地址:0xc420076000, 指針的記憶體地址: 0xc420074000
傳入修改後的WindWheel: {Age:2 Name:GreatBlue}, 記憶體地址:0xc420076000, 指針的記憶體地址: 0xc420074010
調用後原始的WindWheel: {Age:2 Name:GreatBlue}, 記憶體地址:0xc420076000, 指針的記憶體地址: 0xc420074000
這說明golang在使用值傳遞時會為傳入的值創建一個副本存儲,所以該變數的地址會變,如果根據傳入時的工廠模式的值進行取值,是取不到的,由於記憶體地址不同會造成記憶體泄漏。所以應該採取引用傳遞,只會為傳入的指針創建一個副本,並不影響變數的地址更改
此處引用鳥窩前輩的一些總結:
如何選擇 T 和 *T
- 不想變數被修改。 如果你不想變數被函數和方法所修改,那麼選擇類型T。相反,如果想修改原始的變數,則選擇*T
- 如果變數是一個大的struct或者數組,則副本的創建相對會影響性能,這個時候考慮使用*T,只創建新的指針,這個區別是巨大的
- (不針對函數參數,只針對本地變數/本地變數)對於函數作用域內的參數,如果定義成T,Go編譯器儘量將對象分配到棧上,而*T很可能會分配到對象上,這對垃圾回收會有影響
模式分析
將對象的創建和對象本身業務處理分離可以降低系統的耦合度,使得兩者修改起來都相對容易。
簡單工廠模式最大的問題在於工廠類的職責相對過重,增加新的產品需要修改工廠類的判斷邏輯,這一點與開閉原則是相違背的。
簡單工廠模式的要點在於:當你需要什麼,只需要傳入一個正確的參數,就可以獲取你所需要的對象。
簡單工廠模式的優點
工廠類含有必要的判斷邏輯,可以決定在什麼時候創建哪一個產品類的實例,客戶端可以免除直接創建產品對象的責任,而僅僅“消費”產品;簡單工廠模式通過這種做法實現了對責任的分割,它提供了專門的工廠類用於創建對象。
客戶端無須知道所創建的具體產品類的類名,只需要知道具體產品類所對應的參數即可,對於一些複雜的類名,通過簡單工廠模式可以減少使用者的記憶量。
通過引入配置文件,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產品類,在一定程度上提高了系統的靈活性。
簡單工廠模式的缺點
由於工廠類集中了所有產品創建邏輯,一旦不能正常工作,整個系統都要受到影響。
使用簡單工廠模式將會增加系統中類的個數,在一定程式上增加了系統的複雜度和理解難度。
系統擴展困難,一旦添加新產品就不得不修改工廠邏輯,在產品類型較多時,有可能造成工廠邏輯過於複雜,不利於系統的擴展和維護。
簡單工廠模式由於使用了靜態工廠方法,造成工廠角色無法形成基於繼承的等級結構。
適用環境
在以下情況下可以使用簡單工廠模式:
工廠類負責創建的對象比較少:由於創建的對象較少,不會造成工廠方法中的業務邏輯太過複雜。
客戶端只知道傳入工廠類的參數,對於如何創建對象不關心:客戶端既不需要關心創建細節,甚至連類名都不需要記住,只需要知道類型所對應的參數。