Golang的垂直組合思維——type embedding

什么是Golang的正交組合-垂直組合思維:Tony Bai的博客 - Coding in GO way - Orthogonal Composition

Go語言通過type embedding實現(xiàn)垂直組合。組合方式莫過于以下這么幾種:
a):construct interface by embedding interface
b):construct struct by embedding interface
c):construct struct by embedding struct
Go語言中沒有繼承挤土,但是可以用結構體嵌入實現(xiàn)繼承示罗,還有接口這個東西〗匣希現(xiàn)在問題來了:什么場景下應該用繼承,什么場景下應該用接口再扭。這里從一個實際的案例出發(fā)。

問題描述:

網游服務器中的一個例子。假設每個實體都有一個ObjectID付材,每一個實例都有一個獨一無二的ObjectID。用面向對象的觀點圃阳,就是有一個Object對象厌衔,里面有getID()方法,所有對象都是繼承自Object對象捍岳。
Creature繼承Object富寿,表示游戲中的生物。然后像Monster锣夹,Human页徐,都繼承自Creature的。Item也繼承自Object银萍,表示物品類变勇。除了像裝備這種很直觀的物品,尸體這類Corpse也是繼承自Item的贴唇。而尸體又有分MonsterCorpse和HumanCorpse等搀绣。
Effect也繼承自Object,表示效果類滤蝠。比如玩家身上的狀態(tài)豌熄。還有其它很多很多,全是以Object為基類的物咳。
總之锣险,Object是一個最下面的基類蹄皱,直接的派生類很多,派生類的派生類更多芯肤,這樣一顆繼承樹結構巷折。

實現(xiàn)方法:

construct struct by embedding struct

這是最簡單的繼承的方式:

//construct struct by embedding struct
type Object struct{
    ID uint
}
type Creature sturct {
    Object // Creature繼承自Object
}
type Monster struct {
    Creature // Monster繼承自Monster
}

這樣做的好處就是,Monster直接可以調用到Creature里的方法崖咨,Creature直接可以調用Object里的方法锻拘。不用重寫代碼。
但是击蹲,Go中沒有基類指針指向派生類對象署拟,不可以Object指向一個Monster對象,調用Monster中的方法歌豺。
而我們實際上在很多地方需要這種抽象類型機制推穷,比如存儲需要存Creature類型,使用的時候再具體用Monster類型方法类咧。
struct中嵌入struct馒铃,被嵌入的struct的method會被提升到外面的類型中。比如stl中的poolLocal struct痕惋,對于外部來說它擁有了Lock和Unlock方法区宇,但是實際調用時,method調用實際被傳給poolLocal中的Mutex實例值戳。

// sync/pool.go
  type poolLocal struct {
      private interface{}   // Can be used only by the respective P.
      shared  []interface{} // Can be used by any P.
      Mutex                 // Protects shared.
      pad     [128]byte     // Prevents false sharing.
  }

construct interface by embedding interface

我們新建一個工程并定義Object接口:

//object/object.go
package object
type Object interface {
    GetID() uint  
    //每一個Object的實現(xiàn)類型都有一個ID值议谷,通過GetID()獲取其ID
}

Creature也定義為一個接口,他繼承于Object述寡,且擁有自己的方法Create()柿隙。為了體現(xiàn)繼承的關系,我把它放在了子目錄下:

//object/creature/creature.go
package creature
import (
    "fmt"
    "github.com/ovenvan/multi-inheritance/object"
)

type Creature interface {
    object.Object
    Create()
}

同樣Human和Monster都繼承于Creature鲫凶,且擁有各自獨一無二的方法Human.Born()[略]和Monster.Hatch():

//object/creature/monster/monster.go
package monster
import (
    "fmt"
    "github.com/ovenvan/multi-inheritance/object"
    "github.com/ovenvan/multi-inheritance/object/creature"
)
type Monster interface {
    creature.Creature
    Hatch()
}
type Mstr struct{/*some properties*/}

為了使Mstr能夠實現(xiàn)接口Monster禀崖,我們需要為他實現(xiàn)func:

func (this *Mstr) GetID() uint{/*your code*/}  
func (this *Mstr) Create() {/*your code*/}
func (this *Mstr) Hatch(){/*your code*/}
func NewMonster () Monster{return &Monster_s{}}

這樣就不會出現(xiàn)construct struct by embedding struct時出現(xiàn)的基類指針無法指向子類的問題。現(xiàn)在一個東西實現(xiàn)Object螟炫,如果它是Monster波附,那么一定是Creature。但屬于父類的GetID和Create方法是沒法復用的昼钻,也就是說對于Hum struct掸屡,我們仍需要重寫GetID和Create方法。如果這些方法實現(xiàn)基本相同然评,那么將會出現(xiàn)大量冗余代碼仅财。

construct struct by embedding interface

為了復用父類的方法,我們必須把方法定義在父類中碗淌。于是我們就想到了在父類中創(chuàng)建struct供子類繼承盏求,在父類的struct中實現(xiàn)方法func:

//object/object.go
package object
type Object interface {
    GetID() uint
}
type Obj struct {   //needs to be public
    id uint
}
func (this *Obj) GetID() uint{
    return this.id
}

為Creature接口創(chuàng)建的struct Crea 通過construct struct by embedding struct繼承Obj抖锥,如此便繼承了GetID的方法:

//object/creature/creature.go
package creature
import (
    "fmt"
    "github.com/ovenvan/multi-inheritance/object"
)
type Creature interface {
    object.Object
    Create()
}
type Crea struct {
    object.Obj      //struct 中綁定interface和struct的區(qū)別?
                    // Object只實現(xiàn)了一個Obj實例碎罚,這個實例的作用是被繼承磅废,提供父類的代碼,因此應該繼承Obj荆烈,而非Object
}
func (t *Crea) Create(){
    fmt.Println("This is a Base Create Method")
}
func (t *Crea)GetID() uint{  //override
    fmt.Println("Override GetID from Creature")
    return t.Obj.GetID()
    //t.GetID()  it is a recursive call
}

為什么是construct struct by embedding struct而不是construct struct by embedding interface?如果可以實現(xiàn)綁定接口而非實例的話拯勉,我們是否可以不對外公開struct Obj呢。
作者至現(xiàn)在思考的結果是憔购,綁定接口是可行的宫峦。不對外公開struct Obj(換言之,讓使用者無法自如的創(chuàng)建struct Obj)的前提是庫中使用Obj的代碼都與Obj在同一個package中(不符合業(yè)務邏輯倦始,但作者看過的幾個第三方包中的確有將所有代碼寫在一個package甚至一個文件中的情況)斗遏。
之所以在此綁定了Objstruct而非Objectinterface,是因為我們只創(chuàng)建了一個Objectinterface的實例鞋邑,省去了賦值(給interfacestruct)的麻煩。具體而言账蓉,標準庫中的package context中timerCtx綁定的是cancelCtx這一個struct枚碗。

//context.go
package context

type Context interface {
    //......
}

type cancelCtx struct {
    Context
    mu       sync.Mutex            // protects following fields
    done     chan struct{}         // created lazily, closed by first cancel call
    children map[canceler]struct{} // set to nil by the first cancel call
    err      error                 // set to non-nil by the first cancel call
}

type timerCtx struct {
    cancelCtx          //construct struct by embedding struct
    timer *time.Timer   // Under cancelCtx.mu.
    deadline time.Time
}

func (c *timerCtx) cancel(removeFromParent bool, err error) {
    c.cancelCtx.cancel(false, err)  //具體調用
    //......

需要注意的是,具體調用是铸本、timerCtx調用(cancelCtx.cancel)的方法肮雨,而非(timerCtx.cancelCtx)調用cancel方法。(很基礎但很重要)

而如果我們在父類實現(xiàn)多個GetID的方法箱玷,并希望在子類中加以選擇怨规,那么我們就需要創(chuàng)建兩個struct并分別實現(xiàn)不同的方法,使用construct struct by embedding interface來決定綁定哪一個struct锡足。另外波丰,如果使用construct struct by embedding interface,則不可以越過父類的方法(如果存在的話)去執(zhí)行爺類(???)定義的方法舶得。
為什么說在不同package下不公開struct(即struct obj)不可行掰烟,因為不是在同一個package中進行賦值。也就是說必須公開對外可見后沐批,外部才得以使用他來賦值纫骑。而使用NewObj(...)作包裹從本質而言也是一個道理。
最后我們給出Monster的代碼九孩,可以發(fā)現(xiàn)先馆,他只需要實現(xiàn)自己獨有的方法即可。當然它也可以有選擇性的override父類的方法:

//object/creature/monster/monster.go
package monster
import (
    "fmt"
    "github.com/ovenvan/multi-inheritance/object"
    "github.com/ovenvan/multi-inheritance/object/creature"
)
type Monster interface {
    creature.Creature
    Hatch()
}
type Monster_s struct {
    creature.Crea
    alive bool
}
func (t *Monster_s) Hatch(){
    t.Create()
    fmt.Println("After created, i was hatched")
}
func (t *Monster_s) Create(){
    t.Crea.Create()
    fmt.Println("This is an Override Create Method from monster")
}
func (t *Monster_s)GetID() uint{
    fmt.Println("Override GetID from Monster")
    //return t.Crea.GetID()
    return t.Obj.GetID()        //直接調用父類的父類(Obj)的方法躺彬,
                                //跳過了Creature重寫的方法煤墙。
    //t.GetID()  recursive call
}

func NewMonster (m object.ObjectManager) Monster{...}

最后看一下main.go:

package main

import (
    "github.com/ovenvan/multi-inheritance/object/creature/monster"
)

func main() {
    mstr:=monster.NewMonster()
    mstr.Hatch()
}

我在Github-multi-inheritance上傳了本次實驗的Demo缤底,包括完善了各函數(shù)的代碼,大家可以通過

go get github.com/ovenvan/multi-inheritance

下載該Demo并提出修改意見番捂。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末个唧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子设预,更是在濱河造成了極大的恐慌徙歼,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鳖枕,死亡現(xiàn)場離奇詭異魄梯,居然都是意外死亡,警方通過查閱死者的電腦和手機宾符,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門酿秸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人魏烫,你說我怎么就攤上這事辣苏。” “怎么了哄褒?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵稀蟋,是天一觀的道長。 經常有香客問我呐赡,道長退客,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任链嘀,我火速辦了婚禮萌狂,結果婚禮上,老公的妹妹穿的比我還像新娘怀泊。我一直安慰自己茫藏,他們只是感情好,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布包个。 她就那樣靜靜地躺著刷允,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碧囊。 梳的紋絲不亂的頭發(fā)上树灶,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機與錄音糯而,去河邊找鬼天通。 笑死,一個胖子當著我的面吹牛熄驼,可吹牛的內容都是我干的像寒。 我是一名探鬼主播烘豹,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼诺祸!你這毒婦竟也來了觉阅?” 一聲冷哼從身側響起计贰,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤漠酿,失蹤者是張志新(化名)和其女友劉穎赫粥,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胃夏,經...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡轴或,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了仰禀。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片照雁。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖答恶,靈堂內的尸體忽然破棺而出饺蚊,到底是詐尸還是另有隱情,我是刑警寧澤亥宿,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布卸勺,位于F島的核電站,受9級特大地震影響烫扼,放射性物質發(fā)生泄漏。R本人自食惡果不足惜碍庵,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一映企、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧静浴,春花似錦堰氓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至得问,卻和暖如春囤攀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宫纬。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工焚挠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人漓骚。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓蝌衔,卻偏偏與公主長得像榛泛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子噩斟,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內容