Golang的垂直组合思维——type embedding

栏目: Go · 发布时间: 5年前

内容简介:什么是Golang的正交组合-垂直组合思维:网游服务器中的一个例子。假设每个实体都有一个ObjectID,每一个实例都有一个独一无二的ObjectID。用面向对象的观点,就是有一个Object对象,里面有getID()方法,所有对象都是继承自Object对象。Creature继承Object,表示游戏中的生物。然后像Monster,Human,都继承自Creature的。Item也继承自Object,表示物品类。除了像装备这种很直观的物品,尸体这类Corpse也是继承自Item的。而尸体又有分Monster

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

Go语言通过type embedding实现垂直组合。组合方式莫过于以下这么几种:

a):construct interface by embedding interface
b):construct struct by embedding interface
c):construct struct by embedding struct

Go语言中没有继承,但是可以用结构体嵌入实现继承,还有接口这个东西。现在问题来了:什么场景下应该用继承,什么场景下应该用接口。这里从一个实际的案例出发。

问题描述:

网游服务器中的一个例子。假设每个实体都有一个ObjectID,每一个实例都有一个独一无二的ObjectID。用面向对象的观点,就是有一个Object对象,里面有getID()方法,所有对象都是继承自Object对象。

Creature继承Object,表示游戏中的生物。然后像Monster,Human,都继承自Creature的。Item也继承自Object,表示物品类。除了像装备这种很直观的物品,尸体这类Corpse也是继承自Item的。而尸体又有分MonsterCorpse和HumanCorpse等。

Effect也继承自Object,表示效果类。比如玩家身上的状态。还有其它很多很多,全是以Object为基类的。

总之,Object是一个最下面的基类,直接的派生类很多,派生类的派生类更多,这样一颗继承树结构。

实现方法:

constructstructby embeddingstruct

这是最简单的继承的方式:

//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.
  }

constructinterfaceby embeddinginterface

我们新建一个工程并定义Object接口:

//object/object.go
package object
type Object interface {
    GetID() uint  
    //每一个Object的实现类型都有一个ID值,通过GetID()获取其ID
}

Creature也定义为一个接口,他继承于Object,且拥有自己的方法Create()。为了体现继承的关系,我把它放在了子目录下:

//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能够实现接口Monster,我们需要为他实现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{}}

这样就不会出现 construct struct by embedding struct 时出现的基类指针无法指向子类的问题。现在一个东西实现Object,如果它是Monster,那么一定是Creature。但属于父类的GetID和Create方法是没法复用的,也就是说对于Hum struct,我们仍需要重写GetID和Create方法。如果这些方法实现基本相同,那么将会出现大量冗余代码。

constructstructby embeddinginterface

为了复用父类的方法,我们必须把方法定义在父类中。于是我们就想到了在父类中创建struct供子类继承,在父类的struct中实现方法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接口创建的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的区别?
                    // Object只实现了一个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 ?如果可以实现绑定接口而非实例的话,我们是否可以不对外公开struct Obj呢。作者至现在思考的结果是,绑定接口是可行的,但不对外公开struct Obj(换言之,让使用者无法自如的创建struct Obj)是不可行的。

之所以在此绑定了Obj struct 而非Object interface ,是因为我们只创建了一个Object interface 的实例,省去了赋值(给 interfacestruct )的麻烦。而如果我们希望在父类实现多个GetID的方法,并在子类中加以选择,那么我们就需要创建两个 struct 并分别实现不同的方法,使用 construct struct by embedding interface 来决定绑定哪一个 struct 。另外,如果使用 construct struct by embedding interface ,则不可以越过父类的方法(如果存在的话)去执行爷类(???)定义的方法。

为什么说不公开struct(即struct obj)不可行,因为不是在同一个package中进行赋值。也就是说必须公开对外可见后,外部才得以使用他来赋值。而使用NewObj(...)作包裹从本质而言也是一个道理。

最后我们给出Monster的代码,可以发现,他只需要实现自己独有的方法即可。当然它也可以有选择性的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,包括完善了各函数的代码,大家可以通过

go get github.com/ovenvan/multi-inheritance

下载该Demo并提出修改意见。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Advanced Web Metrics with Google Analytics, 2nd Edition

Advanced Web Metrics with Google Analytics, 2nd Edition

Brian Clifton / Sybex / 2010-3-15 / USD 39.99

Valuable tips and tricks for using the latest version of Google Analytics Packed with insider tips and tricks, this how-to guide is fully revised to cover the latest version of Google Analytics and sh......一起来看看 《Advanced Web Metrics with Google Analytics, 2nd Edition》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具