1.前言
面向對象編程的三大特性:封裝、繼承、多態。可見繼承是面向對象程序設計中一個重要的概念。Go 作為面向對象的編程語言,自然也支持繼承。
比較特殊的是 Go 實現繼承的方式與其他傳統 OOP 語言所有不同,不像 C++ 有專門的繼承語法,或者像 Java 中有專門的關鍵字 extends。
C++ 的繼承:
// 基類
class Animal {
public:
void eat();
void sleep();
};
// 子類
class Dog : public Animal {
public:
void bark();
};
Java 的繼承:
// 基類
public class Animal {
public void eat(){};
public void sleep(){};
}
// 子類
public class Dog extends Animal {
public void bark(){};
}
2.嵌入式繼承機制
Go 使用匿名嵌套實現繼承。
我們用很容易理解的動物-貓來舉例子。
type Animal struct {
Name string
}
func (a *Animal) Eat() {
fmt.Printf("%v is eating", a.Name)
fmt.Println()
}
type Cat struct {
Animal
}
cat := Cat{
Animal: Animal{
Name: "cat",
},
}
cat.Eat() // cat is eating
首先,我們實現了一個 Animal 的結構體,代表動物類。并聲明了 Name 字段,用于描述動物的名字。
然后,實現了一個以 Animal 為 receiver 的 Eat 方法,來描述動物進食的行為。
最后,聲明了一個 Cat 結構體,組合了 Cat 字段。再實例化一個貓,調用Eat方法,可以看到會正常的輸出。
可以看到,Cat 結構體本身沒有 Name 字段,也沒有去實現 Eat() 方法。唯一有的就是匿名嵌套的方式繼承了 Animal 父類,至此,我們證明了 Go 通過匿名嵌套的方式實現了繼承。
上面是嵌入類型實例,同樣地也可以嵌入類型指針。
type Cat struct {
*Animal
}
cat := Cat{
Animal: Animal{
Name: "cat",
},
}
3.嵌入式繼承機制的的局限
相比于 C++ 和 Java, Go 的繼承機制的作用是非常有限的,因為沒有抽象方法,有很多的設計方案可以在 C++ 和 Java 中輕松實現,但是 Go 的繼承卻不能完成同樣的工作。
package main
import "fmt"
// Animal 動物基類
type Animal struct {
name string
}
func (a *Animal) Play() {
fmt.Println(a.Speak())
}
func (a *Animal) Speak() string {
return fmt.Sprintf("my name is %v", a.name)
}
func (a *Animal) Name() string {
return a.name
}
// Dog 子類狗
type Dog struct {
Animal
Gender string
}
func (d *Dog) Speak() string {
return fmt.Sprintf("%v and my gender is %v", d.Animal.Speak(), d.Gender)
}
func main() {
d := Dog{
Animal: Animal{name: "Hachiko"},
Gender: "male",
}
fmt.Println(d.Name())
fmt.Println(d.Speak())
d.Play() // Play() 中調用的是基類 Animal.Speak() 方法,而不是 Dog.Speak()
}
運行輸出:
Hachiko
my name is Hachiko and my gender is male
my name is Hachiko
上面的例子中,Dog 類型重寫了 Speak() 方法。然而如果父類型 Animal 有另外一個方法 Play() 調用 Speak() 方法,但是 Dog 沒有重寫 Play() 的時候,Dog 類型的 Speak() 方法則不會被調用,因為 Speak() 方法不是抽象方法,此時繼承無法實現多態。
4.使用接口封裝方法
為了解決上面的問題,我們應該使用接口封裝方法,通過實現接口方法來實現多態。
package main
import (
"fmt"
)
type Animal interface {
Name() string
Speak() string
Play()
}
type Dog struct {
name string
gender string
}
func (d *Dog) Play() {
fmt.Println(d.Speak())
}
func (d *Dog) Speak() string {
return fmt.Sprintf("my name is %v and my gender is %v", d.name, d.gender)
}
func (d *Dog) Name() string {
return d.name
}
func Play(a Animal) {
a.Play()
}
func main() {
d :=Dog{"Hachiko", "male"}
fmt.Println(d.Name())
fmt.Println(d.Speak())
Play(d)
}
運行輸出:
Hachiko
my name is Hachiko and my gender is male
my name is Hachiko and my gender is male
注意:Go 中某個類型需要實現接口中的所有方法才算作實現了接口。
5.小結
如果一個 struct 嵌套了另一個匿名結構體,那么這個結構可以直接訪問匿名結構體的屬性和方法,從而實現繼承。
如果一個 struct 嵌套了另一個有名的結構體,那么這個模式叫做組合。
如果一個 struct 嵌套了多個匿名結構體,那么這個結構可以直接訪問多個匿名結構體的屬性和方法,從而實現多重繼承。
本篇文章就到這里了,希望能幫助到你,也希望您能多多關注腳本之家的更多內容!
您可能感興趣的文章:- Django繼承自帶user表并重寫的例子
- Go語言中nil判斷引起的問題詳析
- 分析Go語言中CSP并發模型與Goroutine的基本使用
- Go遍歷struct,map,slice的實現
- Go 容器遍歷的實現示例