Template Method in Golang

序言

隨著容器云技術(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è)特性分別是:

  1. goroutine和channel
  2. 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)雅:

  1. 簡潔之處在于,Golang并沒有沿襲傳統(tǒng)面向?qū)ο缶幊讨械闹T多概念硬霍,比如繼承帜慢、虛函數(shù)、構(gòu)造函數(shù)和析構(gòu)函數(shù)唯卖、隱藏的this指針等
  2. 優(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è)算法的框架就可重新定義該算法的某些特定步驟谤牡。

模板方法的通用類圖如下所示:

template-method.png

雖然模板方法的通用類圖非常簡單副硅,但它卻是一個(gè)應(yīng)用非常廣泛的設(shè)計(jì)模式。

抽象模板

AbstractClass叫做抽象模板翅萤,它的方法分為兩類:

  1. 基本方法:也叫基本操作恐疲,是由子類實(shí)現(xiàn)的方法,并且在模板方法中被調(diào)用
  2. 模板方法:可以有一個(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ù)操作快耿,核心算法的框架為:

  1. 連接數(shù)據(jù)庫
  2. 查詢用戶輸入的三元組對(duì)應(yīng)的記錄是否存在
  3. 如果不存在囊陡,則插入新紀(jì)錄
  4. 如果存在,則更新記錄
  5. 關(guān)閉數(shù)據(jù)庫

說明:我們同時(shí)假定數(shù)據(jù)庫有數(shù)據(jù)記錄的活性檢測功能掀亥,當(dāng)一條數(shù)據(jù)記錄長期沒有被引用是撞反,就將它從數(shù)據(jù)庫中刪掉,所以用戶不用關(guān)心記錄的刪除操作搪花。

:該問題是筆者自己杜撰的遏片。

建模

根據(jù)Golang的interface特性建模后,模板方法的類圖如下:

template-method-golang.png

下面對(duì)該圖做如下說明:

  1. 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)模式中的抽象模板
  2. 在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):

  1. 用[0, 5]之間的隨機(jī)數(shù)是否小于2來模擬是否查找失敗
  2. 如果查找失敗刨啸,就插入記錄
  3. 如果查找成功堡赔,就更新記錄

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有一定的幫助叛拷。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舌厨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子忿薇,更是在濱河造成了極大的恐慌裙椭,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件煌恢,死亡現(xiàn)場離奇詭異骇陈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瑰抵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來器联,“玉大人二汛,你說我怎么就攤上這事婿崭。” “怎么了肴颊?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵氓栈,是天一觀的道長。 經(jīng)常有香客問我婿着,道長授瘦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任竟宋,我火速辦了婚禮提完,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘丘侠。我一直安慰自己徒欣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布蜗字。 她就那樣靜靜地躺著打肝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挪捕。 梳的紋絲不亂的頭發(fā)上粗梭,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音级零,去河邊找鬼断医。 笑死,一個(gè)胖子當(dāng)著我的面吹牛妄讯,可吹牛的內(nèi)容都是我干的孩锡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼亥贸,長吁一口氣:“原來是場噩夢啊……” “哼躬窜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起炕置,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤荣挨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后朴摊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體默垄,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年甚纲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了口锭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鹃操,靈堂內(nèi)的尸體忽然破棺而出韭寸,到底是詐尸還是另有隱情,我是刑警寧澤荆隘,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布恩伺,位于F島的核電站,受9級(jí)特大地震影響椰拒,放射性物質(zhì)發(fā)生泄漏晶渠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一燃观、第九天 我趴在偏房一處隱蔽的房頂上張望褒脯。 院中可真熱鬧,春花似錦仪壮、人聲如沸憨颠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爽彤。三九已至,卻和暖如春缚陷,著一層夾襖步出監(jiān)牢的瞬間适篙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國打工箫爷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嚷节,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓虎锚,卻偏偏與公主長得像硫痰,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子窜护,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法效斑,類相關(guān)的語法,內(nèi)部類的語法柱徙,繼承相關(guān)的語法缓屠,異常的語法,線程的語...
    子非魚_t_閱讀 31,631評(píng)論 18 399
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,127評(píng)論 25 707
  • 最近玩狼人殺上癮了护侮,荒廢時(shí)光敌完。 穩(wěn)穩(wěn)度過產(chǎn)品評(píng)審。好累 好困
    琛周閱讀 230評(píng)論 0 0
  • 好的羊初,剛剛結(jié)束三天的蘇州周莊三天的獨(dú)自旅游滨溉,因?yàn)槭亲约邯?dú)自的自助游,印象格外深刻,了解的也更加詳細(xì)业踏,所以就趕快把熱...
    BeautyYao閱讀 1,265評(píng)論 0 9