序言
隨著容器云技術(shù)的不斷成熟川尖,微服務(wù)架構(gòu)也變得越來越火登下。在微服務(wù)架構(gòu)下,我們將原本單一的應(yīng)用按照功能邊界分解成一系列獨(dú)立叮喳、專注的微服務(wù)被芳。每個(gè)微服務(wù)對(duì)應(yīng)傳統(tǒng)應(yīng)用中的一個(gè)組件,但是可以獨(dú)立編譯馍悟、部署和擴(kuò)展畔濒。每個(gè)團(tuán)隊(duì)可以根據(jù)自身服務(wù)的需求和行業(yè)發(fā)展的現(xiàn)狀,自由選擇最適合的技術(shù)棧赋朦,比如編程語言和數(shù)據(jù)庫篓冲。Golang語言這幾年風(fēng)華正茂李破,不僅kubernetes、 openshif和docker等容器云相關(guān)的開源項(xiàng)目的開發(fā)語言是Golang壹将,而且很多對(duì)實(shí)時(shí)性要求不高的微服務(wù)團(tuán)隊(duì)也選擇Golang作為主要開發(fā)語言嗤攻。
在這個(gè)大背景下,筆者也開始了Golang之旅诽俯。Golang作為一門全新的靜態(tài)類型開發(fā)語言妇菱,與既有的開發(fā)語言相比簡潔、有趣卻又超級(jí)強(qiáng)大暴区,具備眾多令人興奮不已的新特性闯团,其中最令筆者興奮的兩個(gè)特性分別是:
- goroutine和channel
- interface
如果說goroutine和channel是支撐起Golang的并發(fā)模型的基石,讓Golang在如今集群化與多核化的時(shí)代成為一道極為亮麗的風(fēng)景仙粱,那么interface是Golang整個(gè)類型系統(tǒng)的基石房交,讓Golang在基礎(chǔ)編程哲學(xué)的探索上達(dá)到前所未有的高度。
Golang的設(shè)計(jì)哲學(xué)之一是“少即是多”伐割,沒有萬能的語言候味,保持簡單性的方法就是只提供一種方法做事情,把事情做到極致隔心。在這個(gè)原則的指導(dǎo)下白群,Golang對(duì)于面向?qū)ο缶幊痰闹С址浅:啙嵍鴥?yōu)雅:
- 簡潔之處在于,Golang并沒有沿襲傳統(tǒng)面向?qū)ο缶幊讨械闹T多概念硬霍,比如繼承帜慢、虛函數(shù)、構(gòu)造函數(shù)和析構(gòu)函數(shù)唯卖、隱藏的this指針等
- 優(yōu)雅之處在于粱玲,Golang對(duì)面向?qū)ο缶幊痰闹С质钦Z言類型系統(tǒng)中的天然組成部分,整個(gè)類型系統(tǒng)通過interface串聯(lián)耐床,渾然一體密幔。多數(shù)語言都提供interface,但它們的interface都不同于Golang的interface撩轰,Golang中的interface與其他語言最大的一點(diǎn)區(qū)別是它的非侵入性。
筆者將要開發(fā)的用戶故事涉及一系列算法步驟昧廷,即多個(gè)網(wǎng)絡(luò)平面操作的算法框架是相同的堪嫂,而具體步驟中的行為有些差異,于是就想到了一種設(shè)計(jì)模式——模板方法木柬。與其他面向?qū)ο笳Z言不同的是皆串,Golang天生就是組合式設(shè)計(jì)。
本文嘗試用Golang實(shí)現(xiàn)一下模板方法眉枕,與讀者共同體驗(yàn)一下組合式設(shè)計(jì)的魅力恶复。
模板方法回顧
定義
模板方法模式(Template Method Pattern)是定義一個(gè)操作中的算法的框架怜森,而將一些步驟延遲到子類中,使得子類可以不改變一個(gè)算法的框架就可重新定義該算法的某些特定步驟谤牡。
模板方法的通用類圖如下所示:
雖然模板方法的通用類圖非常簡單副硅,但它卻是一個(gè)應(yīng)用非常廣泛的設(shè)計(jì)模式。
抽象模板
AbstractClass叫做抽象模板翅萤,它的方法分為兩類:
- 基本方法:也叫基本操作恐疲,是由子類實(shí)現(xiàn)的方法,并且在模板方法中被調(diào)用
- 模板方法:可以有一個(gè)或幾個(gè)套么,一般是一個(gè)算法框架培己,實(shí)現(xiàn)對(duì)基本方法的調(diào)度,完成固定的邏輯胚泌。
下面以C++代碼為例:
//AbstractClass.h
struct AbstractClass
{
virtual ~AbstractClass() = default;
void templateMethod();
private:
virtual void doAnyThing() = 0;
virtual void doSomeThing() = 0;
};
//AbstractClass.cpp
void AbstractClass::templateMethod()
{
doAnyThing();
doSomeThing();
}
具體模板
ConcreteClass1和ConcreteClass2屬于具體模板省咨,實(shí)現(xiàn)父類所定義的一個(gè)或多個(gè)抽象方法,也就是父類定義的基本方法在子類中得以實(shí)現(xiàn)玷室。
下面以C++代碼為例:
//ConcreteClass1.h
struct ConcreteClass1 : AbstractClass
{
private:
virtual void doAnyThing() override;
virtual void doSomeThing() override;
};
//ConcreteClass1.cpp
void ConcreteClass1::doAnyThing()
{
...
}
void ConcreteClass1::doSomeThing()
{
...
}
注:ConcreteClass2和ConcreteClass2的代碼類似茸炒,我們不再贅述。
實(shí)例化模板方法
引入問題
假設(shè)我們有兩個(gè)數(shù)據(jù)庫阵苇,即Mysql和Oracle壁公,用戶操作時(shí)有相同的算法框架,而算法步驟的具體行為不同绅项,比如數(shù)據(jù)庫的連接接口和關(guān)閉接口實(shí)現(xiàn)不同紊册。
我們假定用戶的輸入是三元組(table, key, value),期望數(shù)據(jù)庫有一個(gè)事務(wù)操作快耿,核心算法的框架為:
- 連接數(shù)據(jù)庫
- 查詢用戶輸入的三元組對(duì)應(yīng)的記錄是否存在
- 如果不存在囊陡,則插入新紀(jì)錄
- 如果存在,則更新記錄
- 關(guān)閉數(shù)據(jù)庫
說明:我們同時(shí)假定數(shù)據(jù)庫有數(shù)據(jù)記錄的活性檢測功能掀亥,當(dāng)一條數(shù)據(jù)記錄長期沒有被引用是撞反,就將它從數(shù)據(jù)庫中刪掉,所以用戶不用關(guān)心記錄的刪除操作搪花。
注:該問題是筆者自己杜撰的遏片。
建模
根據(jù)Golang的interface特性建模后,模板方法的類圖如下:
下面對(duì)該圖做如下說明:
- Golang的interface只包含純方法聲明撮竿,那么模板方法不能在interface里定義吮便,所以必須增加一個(gè)類(struct),我們記作DbTrans幢踏。DbTrans通過成員變量持有了接口Db髓需,該成員變量在運(yùn)行時(shí)動(dòng)態(tài)綁定具體的數(shù)據(jù)庫實(shí)例,我們用單向關(guān)聯(lián)表示這種關(guān)系房蝉。圖中DbTrans的Exec()方法用于實(shí)現(xiàn)算法框架僚匆,對(duì)應(yīng)模式中的抽象模板
- 在Golang中微渠,一個(gè)類只要實(shí)現(xiàn)了一個(gè)接口要求的所有函數(shù),我們就說這個(gè)類實(shí)現(xiàn)了該接口咧擂,否則就說這個(gè)類和該接口沒有關(guān)系逞盆。類和接口之間沒有強(qiáng)制的契約關(guān)系(繼承),通過組合和動(dòng)態(tài)綁定的方式來實(shí)現(xiàn)繼承和多態(tài)屋确,我們用虛線表示這種隱形的繼承關(guān)系纳击。圖中MySql和Oracle分別實(shí)現(xiàn)了接口Db,對(duì)應(yīng)模式中的具體模板
實(shí)現(xiàn)
Db
Db是核心要素interface攻臀,實(shí)現(xiàn)代碼為:
type Db interface {
Connect()
Close()
InsertRecord(tableName, key, value string)
GetRecord(tableName, key string) (err error, value string)
UpdateRecord(tableName, key, value string)
}
DbTrans
DbTrans是承載模板方法的struct焕数,實(shí)現(xiàn)代碼為:
type DbTrans struct {
Inst Db
}
func (this *DbTrans) Exec(tableName, key, value string) {
if this.Inst == nil {
return
}
this.Inst.Connect()
if err, _ := this.Inst.GetRecord(tableName, key); err != nil {
this.Inst.InsertRecord(tableName, key, value);
} else {
this.Inst.UpdateRecord(tableName, key, value)
}
this.Inst.Close()
}
說明:該算法框架和“引入問題”一節(jié)中給出的框架完全一樣
Mysql
MySql是承載具體模板方法的struct,代碼實(shí)現(xiàn)如下:
type Mysql struct {
}
func (_ *Mysql) Connect() {
fmt.Println("mysql connect...")
}
func (_ *Mysql) Close() {
fmt.Println("mysql close...\n")
}
func (_ *Mysql) InsertRecord(tableName, key, value string) {
fmt.Printf("mysql tablename-%v insert record(%v, %v) succ!\n", tableName, key, value)
}
func (_ *Mysql) GetRecord(tableName, key string) (err error, value string) {
i := rand.Intn(5)
if i < 2 {
fmt.Println("mysql", tableName, "table get record by", key, "failed!")
return errors.New("record is not existed"), "nop"
}
fmt.Println("mysql", tableName, "table get record by", key, "succ!")
return nil, "nop"
}
func (_ *Mysql) UpdateRecord(tableName, key, value string) {
fmt.Printf("mysql tablename-%v update record(%v, %v) succ!\n", tableName, key, value)
}
我們看看Mysql中的GetRecord方法實(shí)現(xiàn):
- 用[0, 5]之間的隨機(jī)數(shù)是否小于2來模擬是否查找失敗
- 如果查找失敗刨啸,就插入記錄
- 如果查找成功堡赔,就更新記錄
Oracle
Oracle與Mysql類似,不再贅述设联。
Client
Client在main函數(shù)里實(shí)現(xiàn)善已,代碼實(shí)現(xiàn)如下:
func main() {
trans := new(DbTrans)
trans.Inst = new(Mysql)
trans.Exec("department", "cloudman", "a architect and be good at openstack")
trans.Inst = new(Oracle)
trans.Exec("department", "cloudman", "a architect and be good at openstack and like dancing")
}
顯然,用戶分別觸發(fā)了兩個(gè)數(shù)據(jù)庫的事務(wù)操作离例,一個(gè)是mysql换团,一個(gè)是oracle,這兩個(gè)事務(wù)的算法框架都是DbTrans宫蛆。
日志
運(yùn)行模板方法的Golang代碼艘包,日志如下:
mysql connect...
mysql department table get record by cloudman failed!
mysql tablename-department insert record(cloudman, a architect and be good at openstack) succ!
mysql close...
oracle connect...
oracle department table get record by cloudman succ!
oracle tablename-department update record(cloudman, a architect and be good at openstack and like dancing) succ!
oracle close...
小結(jié)
本文先回顧了模板方法的通用定義和實(shí)現(xiàn),然后引入一個(gè)問題耀盗,通過建模和實(shí)現(xiàn)等步驟實(shí)例化了Golang版的模板方法想虎,這對(duì)筆者即將開發(fā)的用戶故事有一定的價(jià)值,也希望對(duì)讀者深刻理解Golang的interface有一定的幫助叛拷。