[golang note] 匿名组合

匿名组合



golang也提供了继承机制,但采用组合的文法,因此称为匿名组合。与其他语言不同, golang很清晰地展示出类的内存布局是怎样的。

• 非指针方式组合

?  基本语法

// 基类
type Base struct {
    // 成员变量
}

func (b *Base) 函数名(参数列表) (返回值列表) {
    // 函数体
}

// 派生类
type Derived struct {
    Base
    // 成员变量
}

func (b *Derived) 函数名(参数列表) (返回值列表) {
    // 函数体
}

?  继承规则

 在派生类没有改写基类的成员方法时,相应的成员方法被继承。

 派生类可以直接调用基类的成员方法,譬如基类有个成员方法为Base.Func(),那么Derived.Func()等同于Derived.Base.Func()

 倘若派生类的成员方法名与基类的成员方法名相同,那么基类方法将被覆盖或叫隐藏,譬如基类和派生类都有成员方法Func(),那么Derived.Func()将只能调用派生类的Func()方法,如果要调用基类版本,可以通过Derived.Base.Func()来调用。

? 示例如下

package main

import "fmt"

type Base struct {
}

func (b *Base) Func1() {
    fmt.Println("Base.Func1() was invoked!")
}

func (b *Base) Func2() {
    fmt.Println("Base.Func2() was invoked!")
}

type Derived struct {
    Base
}

func (d *Derived) Func2() {
    fmt.Println("Derived.Func2() was invoked!")
}

func (d *Derived) Func3() {
    fmt.Println("Derived.Func3() was invoked!")
}

func main() {
    d := &Derived{}
    d.Func1()      // Base.Func1() was invoked!
    d.Base.Func1() // Base.Func1() was invoked!

    d.Func2()      // Derived.Func2() was invoked!
    d.Base.Func2() // Base.Func2() was invoked!

    d.Func3() // Derived.Func3() was invoked!
}

?  内存布局

 golang很清晰地展示类的内存布局是怎样的,即Base的位置即基类成员展开的位置。

 golang还可以随心所欲地修改内存布局,即Base的位置可以出现在派生类的任何位置。

? 示例如下

package main

import "fmt"

type Base struct {
    BaseName string
}

func (b *Base) PrintName() {
    fmt.Println(b.BaseName)
}

type Derived struct {
    DerivedName string
    Base
}

func (d *Derived) PrintName() {
    fmt.Println(d.DerivedName)
}

func main() {
    d := &Derived{}
    d.BaseName = "BaseStruct"
    d.DerivedName = "DerivedStruct"
    d.Base.PrintName() // BaseStruct
    d.PrintName()      // DerivedStruct
}

• 指针方式组合


?  基本语法

// 基类
type Base struct {
    // 成员变量
}

func (b *Base) 函数名(参数列表) (返回值列表) {
    // 函数体
}

// 派生类
type Derived struct {
    *Base
    // 成员变量
}

func (b *Derived) 函数名(参数列表) (返回值列表) {
    // 函数体
}

?  继承规则

 基类采用指针方式的组合,依然具有派生的效果,只是派生类创建实例的时候需要外部提供一个基类实例的指针。

 其他规则与非指针方式组合一致。

? 示例如下

package main

import (
    "fmt"
    "log"
    "os"
)

type MyJob struct {
    Command string
    *log.Logger
}

func (job *MyJob) Start() {
    job.Println("job started!") // job.Logger.Println

    fmt.Println(job.Command)

    job.Println("job finished!") // job.Logger.Println
}

func main() {
    logFile, err := os.OpenFile("./job.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0)
    if err != nil {
        fmt.Println("%s", err.Error())
        return
    }
    defer logFile.Close()

    logger := log.New(logFile, "[info]", log.Ldate|log.Ltime|log.Llongfile)
    job := MyJob{"programming", logger}

    job.Start()
    job.Println("test finished!") // job.Logger.Println
}

在经过合适的赋值后,MyJob类型的所有成员方法可以很方便地借用所有log.Logger提供的方法。这对于MyJob的实现者来说,根本就不用意识到log.Logger类型的存在,这就是匿名组合的一个魅力所在。

一些总结


• 名字覆盖

上面说明了派生类成员方法名与基类成员方法名相同时基类方法将被覆盖的情况,这对于成员变量名来说,规则也是一致的。

package main

import "fmt"

type Base struct {
    Name string
}

type Derived struct {
    Base
    Name string
}

func main() {
    d := &Derived{}
    d.Name = "Derived"
    d.Base.Name = "Base"

    fmt.Println(d.Name)      // Derived
    fmt.Println(d.Base.Name) // Base
}

• 名字冲突

匿名组合相当于以其类型名称(去掉包名部分)作为成员变量的名字。那么按此规则,类型中如果存在两个同名的成员,即使类型不同,但我们预期会收到编译错误。

package main

import "log"

type Logger struct {
    Level int
}

type MyJob struct {
    *Logger
    Name string
    *log.Logger // duplicate field Logger
}

func main() {
    job := &MyJob{}
}
时间: 2024-10-13 20:00:11

[golang note] 匿名组合的相关文章

golang中的匿名组合

确切地说,Go语言也提供了继承,但是采用了组合的文法,所以我们将其称为匿名组合: type Base struct { Name string } func (base *Base) Foo() {...} func (base *Base) Bar() {...} type Foo struct { Base ... } func (foo *Foo) Bar() { foo.Base.Bar() ... } 以上代码定义了一个Base类(实现了Foo()和Bar()两个成员方法),然后定义了

[golang note] 接口使用

侵入式接口 √ 在其他一些编程语言中,接口主要是作为不同组件之间的契约存在,即规定双方交互的规约. √ 对契约的实现是强制的,即必须确保用户的确实现了该接口,而实现一个接口,需要从该接口继承. √ 如果一个类实现了接口A,即便另一个接口B与A的方法列表相同,甚至连接口名都相同,但位于不同的命名空间下,那么编译器并不认为这两个接口是一样的. √ 所谓侵入的主要表现在于实现继承接口的类需要明确声明自己实现自某个接口. √ 侵入式接口常纠结的问题是:应该提供哪些接口好呢?如果两个类实现了相同的接口,应

[golang note] 函数定义

普通函数定义 √ golang函数基本组成:关键字func.函数名.参数列表.返回值.函数体和返回语句. • 语法如下 func 函数名(参数列表) (返回值列表) { // 函数体 } • 示例如下 package main import "fmt" import "errors" func Add(a int, b int) (ret int, err error) { if a < 0 || b < 0 { // 假定只支持两个非负数字的加法 er

区块链技术语言(二十四)——Go语言面向对象:匿名组合

继承也是面向对象的三大基本特性之一.通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”.“父类”或“超类”.通过继承,不仅可以让某个类型的对象拥有属于自己的数据结构和操作,还会自动拥有父类的数据结构和操作.这使得现有的类在无需重新编写原来类的情况下对这些功能进行了扩展,很好地解决了代码的重用问题.继承机制的魅力和强大在于它允许程序员利用已经存在的类,并且可以以某种方式扩展这个类,而且对其他继承了这个父类属性和方法的对象没有影响.但Go语言没有继承这个概念,它通过匿名组合间接实现了

【纯血Golang】 通过组合嵌入实现代码共用

应用开发中的一个常见情景,为了避免简单重复,需要在基类中实现共用代码,着同样有助于后期维护. 如果在以往的支持类继承的语言中,比如c++,Java,c#等,这很简单!可是go不支持继承,只能mixin嵌入,且看下面的代码: type ManKind interface{ Say(s string); GetMouth()string } type Man struct{ ManKind } func NewMan() ManKind{ return &Man{}; } func (this *M

Golang之匿名函数和闭包

Go语言支持匿名函数,即函数可以像普通变量一样被传递或使用. 使用方法如下: main.go package main import ( "fmt" ) func main() { var v func(a int) int v = func(a int) int { return a * a } fmt.Println(v(6)) //两种写法 v1 := func(i int) int { return i * i } fmt.Println(v1(7)) } GO语言的匿名函数就

golang 使用匿名结构体的问题

golang允许使用匿名结构体,形如 type Test struct { param1 struct { param2 string } } 一般在使用的时候可以直接这样初始化 a := Test{ param1: struct{ param2 string }{param2: "test"}, } 或者 b := new(Test) b.param1.param2 = "test" 但今天遇到一种情况 匿名结构体的成员上有tag声明,形如 type Test s

golang之匿名函数结合defer

defer语句中的函数会在return语句更新返回值变量后再执行,又因为在函数中定义的匿名函数可以访问该函数包括返回值变量在内的所有变量,所以,对匿名函数采用defer机制,可以使其观察函数的返回值. 以double函数为例: func double(x int) int { return x + x } 我们只需要首先命名double的返回值,再增加defer语句,我们就可以在double每次被调用时,输出参数以及返回值. func double(x int) (result int) { d

goLang(匿名函数)

go 匿名函数与其他语言基本上是无区别的,下面举个例子说明 package main import ( "fmt" ) func main() { a1,a2 := 1,2 rs := func(a int, b int) int { //()括号中是接受的参数 a1,a2 return a+b }(a1, a2) //() 括号中的是传入的参数 fmt.Println(rs) //调用匿名函数 结果输出3 //下面匿名函数不穿入参数,结果输出 rs2 func () { fmt.Pr