Bootstrap

golang中接口的面向对象(二)--继承

在上一节 golang中接口的面向对象(一)–多态特征中,我们学习了galang中的多态,

那么这一节中,我们来学习继承。
一般我们可以使用匿名字段嵌入结构体中来实现继承;

type geometry interface {
	sayHi()
}

type rect struct {
    geometry                   //这里使用匿名字段,使得rect 继承了 geometry 类型
    len, wid float32
}

或者匿名指针(接口类型是一种抽象类型,不能被实例化,所以 *geometry 类型是不存在的)

type geometry interface {
	sayHi()
}

type rect struct {
    geometry                   //这里使用匿名字段,使得rect 继承了 geometry 类型
    len, wid float32           //如果匿名字段是接口类型,则不能使用 *geometry
}
type circle struct {
    *rect                     //这里使用匿名字段,使得circle 继承了 rect 类型
    radius float32
}

不论嵌入 rect 还是 rect 都是一样的,因为rect是可以被实例化的,所以rect是存在的,
在继承中,使用rect 还是 *rect 都是一样的。

我们继续用上一节中的例子,我们修改上一节中的示例代码,如下所示:

type geometry interface {
    sayHi()                     //geometry 只有一个方法sayHi()
}

type rect struct {
    geometry                   //这里使用匿名字段,使得rect 继承了 geometry 类型
    len, wid float32
}

func (r rect) sayHi() {             //这里rect 作为继承者,重写了sayHi方法
   fmt.Println("i am a rect")
}

func (r rect) area() float32 {        //这里rect 作为geometry 的超集,增加了area方法
    return r.len * r.wid
}

func (r rect) perim() float32 {      //这里rect 作为geometry 的超集,增加了perim方法
    return 2 * (r.len + r.wid)
}

type circle struct {
    rect                                   //这里使用匿名字段,使得circle 继承了 rect 类型
    radius float32
}

func show(name string, param interface{}) {

    switch param.(type) {
        case circle:
            fmt.Printf("[c]area of %v is %v \n", name, param.(circle).area())

            fmt.Printf("[c]perim of %v is %v \n", name, param.(circle).perim())

        case rect:
            fmt.Printf("[r]area of %v is %v \n", name, param.(rect).area())

            fmt.Printf("[r]perim of %v is %v \n", name, param.(rect).perim())

        default:
            fmt.Println("wrong type!")
    }
}

func main() {
    rec := rect{
        len: 1,
        wid: 2,
    }

    show("rect", rec)

    cir := circle{
        radius: 1,
    }
    show("circle", cir)

}

输出:

i am a rect

[r]area of rect is 2 

[r]perim of rect is 6 

i am a rect              

[c]area of circle is 0 

[c]perim of circle is 0 

circle 继承了 rect 类型的属性以及方法,因为circle类型长宽属性均为0,所以area 、perim 计算结果都是0

如果我们在rect类型对sayHi方法重写之前,加上下面这段代码,大家考虑一下会出现什么结果?

func (r geometry) sayHi() {
    fmt.Println("i am a geometry")
}

编译器会报invalid receiver type geometry (geometry is an interface type)这样的错误,

说明interface 类型不能作为方法的接收者,此刻它内部的方法是并未实现的,

从上面的例子,我们可以看到rect类型继承了geometry 类型的sayHi方法,并在之后重写了该方法,并且rect还增加了两个新的方法area以及perim。

而circle类型则继承了rect类型的所有属性以及方法(仅仅作为一个例子,其实circle是不可能存在长、宽属性的)。

我们来修改一下上面的代码,show函数main函数保持不变,

type geometry interface {
    sayHi() 
}

type rect struct {
    len, wid float32
}

func (r rect) sayHi() {
    fmt.Println("i am a rect")
}

func (r rect) area() float32 {
    return r.len * r.wid
}

func (r rect) perim() float32 {
    return 2 * (r.len + r.wid)
}

type circle struct {
    rect                                //这里circle 继承了 rect的属性len, wid 以及方法sayHi、area、perim
    radius float32
}

func (c circle) area() float32 {            //这里circle 重写了 area方法
    return math.Pi * c.radius * c.radius
}

func (c circle) perim() float32 {         //这里circle 重写了 perim方法
    return 2 * math.Pi * c.radius
}

注意:上述代码中circle 并没有重写sayHi方法。

输出如下:

i am a rect

[r]area of rect is 2 

[r]perim of rect is 6 

i am a rect

[c]area of circle is 3.1415927        // 重写了 area方法,计算结果改变

[c]perim of circle is 6.2831855     // 重写了 perim方法,计算结果改变

我们修改代码,show函数main函数保持不变,为circle 增加对sayHi方法的重写,

type circle struct {
    rect
    radius float32

}

func (r circle) sayHi() {                    //这里circle 重写了 sayHi方法
    fmt.Println("i am a circle")
}

func (c circle) area() float32 {
    return math.Pi * c.radius * c.radius

}

func (c circle) perim() float32 {
    return 2 * math.Pi * c.radius
}

输出:

i am a rect

[r]area of rect is 2 

[r]perim of rect is 6 

i am a circle                   // 重写了 sayHi方法,打印发生改变

[c]area of circle is 3.1415927 

[c]perim of circle is 6.2831855 

类型的变换

我们再一次修改代码,只修改main函数,其它代码不变,

func main() {

    var ige geometry    //接口类型
    var irec rect
    var irec rect
	irec.len = 1
	irec.wid = 2
    var icir circle
    icir.len = 0               //匿名字段的访问,有一点特别,以后的文章中我们再来学习
    icir.wid = 0              //匿名字段的访问,有一点特别
    icir.radius = 1

    fmt.Printf("ige addr is %p\n", &ige)  
    fmt.Printf("irec addr is %p\n", &irec)
    fmt.Printf("icir addr is %p\n", &icir)

    //ige.sayHi()          //报错,ige是一个接口类型,其本身并未实现sayHi方法

    ige = irec           // rect转换为geometry类型,看起来是不是怀疑是一个指针传递?稍后我们再看
    fmt.Printf("ige addr is %p\n", &ige)       
    ige.sayHi()
    //ige.len = 1      //报错ige.len undefined (type geometry has no field or method len)
    show("rect", ige)

    ige = icir            //circle转换为geometry 类型  
    fmt.Printf("ige addr is %p\n", &ige)
    ige.sayHi()
    //ige.wid= 4          //报错
    //ige.radius= 4      //报错 
    show("circle", ige)
    
    //irec = icir         //报错,无法进行类型转换
    //icir = irec         //报错,无法进行类型转换

}

输出:

ige addr is 0xc000088240

irec addr is 0xc0000a20b0

icir addr is 0xc0000a20c0

ige addr is 0xc000088240

i am a rect

[r]area of rect is 2 

[r]perim of rect is 6 

ige addr is c000088240

i am a circle

[c]area of circle is 3.1415927 

[c]perim of circle is 6.2831855 

从上面代码可以看出,对ige = icir 赋值的操作,是一个值传递。

我们可以看到从一个超集(rect类型)赋值到 子集(geometry 类型),并没有发生内存溢出,

大家可以思考一下,这是为什么?

;