go中介面與繼承的選擇

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Go語言中沒有繼承,但是可以用結構體嵌入實現繼承,還有介面這個東西。現在問題來了:什麼情境下應該用繼承,什麼情境下應該用介面。

問題描述

這裡從一個實際的案例出發。網遊伺服器中的一個例子。假設每個實體都有一個ObjectID,packet中都有使用到這個ObjectID,用戶端與服務端之間通過這個ObjectID知道是一個什麼實體。用物件導向的觀點,就是有一個Object對象,裡面有getObjectID()方法,所有對象都是繼承自Object對象。

Creature繼承Object,表示遊戲中的生物。然後像Monster,NPC,都繼承自Creature的。玩家分為三個種族,Slayer/Vampire/Ouster三個不同的類實現,繼承自Creature。

Item也繼承自Object,表示物品類。除了像裝備這種很直觀的物品,屍體這類Corpse也是繼承自Item的。而屍體又有分MonsterCorpse和SlayerCorpse/VampireCorpse這種玩家屍體。

Effect也繼承自Object,表示效果類。比如玩家身上的狀態。還有其它很多很多,全是以Object為基類的。

總之,Object是一個最下面的基類,直接的衍生類別很多,衍生類別的衍生類別更多,這樣一顆繼承樹結構。

第一次嘗試

首先是用繼承的方式來。

type Object uint64type Creature sturct {    Object // Creature繼承自Object}type Monster struct {    Creature // Monster繼承自Monster}

這樣做的好處是,Monster直接可以調用到Creature裡的方法,Creature直接可以調用Object裡的方法。不用重寫代碼,就是繼承的好處啦。

但是...Go中沒有基類指標指向衍生類別對象,不可以*Object指向一個*Monster對象,調用Monster中的方法。

而我實際上在很多地方需要這種抽象類別型機制,比如儲存需要存Creature類型,使用的時候再具體用Monster類型方法。

第二次嘗試

這次用介面的方式:

type Object interface {    ObjectID() uint}

遊戲中的每個實體的特徵是有一個ObjectID,所以這次把所以實現了ObjectID方法的,都是一個Object。

好啦,這樣就可以存不同的Object了:

objs []Objectswitch objs[i].(type) {    case Monster:    case Item:}

還是使用繼承以實現方法重用:

type Monster struct {    Creature // 繼承Creature。實現Object}

但是...新的問題出現了。比如說給將Monster賦值給Object:

var obj Objectvar monster Monsterobj = monsterif _, ok := obj.(Creature); ok {    // 居然不ok}

obj是一個Monster,Monster繼承Creature,但是obj卻不是一個Creature,what the fuck?

別小看這個問題,因為它滿足不了商務邏輯。比如說:

func f(obj Object) {    // 如果obj是Creature,做邏輯A    // 如果obj是Item,做邏輯B}// obj實際上是Monster,或者Player,或者MonsterCorpse,或Corpse,或WearItem等最具體的衍生類別// 但是沒有辦法知道obj到底是Creature或者是Item

第三次嘗試

第二次嘗試中,對Object使用了介面,但是不徹底。導致了一個Object是Monster卻不是Creature。如果全部介面化會怎麼樣?

type Object interfacetype Creature interface {    Object  // 嵌入Object介面,一個東西實現Creature必須是實現Object的    XXX()   // Creature自身的方法XXX}type Monster interface {    Creature // 一個東西實現Monster必須是實現Creature的    YYY()   // 成為Monster需要實現的方法}

額,好像可以了,現在一個東西實現Object,如果它是Monster,那麼一定是Creature。

但是...沒有用繼承了。對所有最具體的衍生類別,都需要把所有這寫方法實現一遍,這肯定不靠譜。

答案

回頭想一下,總結問題所在:

  • 第一次嘗試中,只用了繼承,無法滿足需求。我需要儲存的時候存放基類,使用的時候使用衍生類別。
  • 在第三次嘗試中,只使用了介面,可以實現,但是要寫很多重複代碼。
  • 第二次介面與繼承都有用到,但是好像不對。

總結下來就是,為了不重寫代碼,必須使用繼承。而為了儲存抽象類別型,調用具體類型的方法,必須用介面。

好好整理一番好,我找到了正確的方式,把介面和繼承分開:

type ObjectInterface interface {    // 所有能夠返回物件類型的,都實現了Object介面。    // 包括OBJECT_CLASS_ITEM/OBJECT_CLASS_CREATURE/OBJECT_CLASS_EFFECT等    ObjectClass() ObjectClass }type Object uint64type Creature struct {    Object  // 繼承Object對象}func (c Creature) ObjectClass() { // 實現ObjectInterface介面    return OBJECT_CLASS_CREATURE}type Item struct {    Object // 繼承Object對象}func (i Item) ObjectClass() { // 實現ObjectInterface介面    return OBJECT_CLASS_ITEM}

CreatureInterface介面也類似。Monster和NPC以及玩家都是實現CreatureInterface介面,並繼承Creature對象的。想寫的話,讓CreatureInterface介面必須是Object。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.