通过组合实现类的继承和方法重写
Go 语言不是像 PHP 等传统面向编程实现那样通过 extends
关键字来显式定义子类与父类之间的继承关系,而是通过组合方式实现类似功能,显式定义继承关系的弊端有两个:一个是导致类的层级复杂,另一个是影响了类的扩展性,设计模式里面推荐的也是通过组合来替代继承提高类的扩展性。
我们来看一个例子,现在有一个父类 Animal
,有一个属性 name
用于表示名称,和三个成员方法,分别用来获取动物叫声、喜欢的食物和动物的名称:
type Animal struct {
name string
}
func (a Animal) Call() string {
return "动物的叫声..."
}
func (a Animal) FavorFood() string {
return "爱吃的食物..."
}
func (a Animal) GetName() string {
return a.name
}
如果我们要定义一个继承自该类的子类 Dog
,可以这么做:
type Dog struct {
Animal
}
func (d Dog) FavorFood() string {
return "骨头"
}
func (d Dog) Call() string {
return "汪汪汪"
}
我们在 Dog
结构体中,引入了 Animal
这个类型,这样一来,我们就可以在 Dog
类中访问所有 Animal
类型包含的属性和方法(如果两个类不在同一个包中,只能访问父类中首字母大写的公共属性和方法),比如这里我们可以在 Dog
实例上直接访问 Animal
类的 GetName
方法。
还可以通过在子类中定义同名方法来覆盖父类的实现,比如这里的 Call
方法和 FavorFood
方法,当我们在 Dog
实例上调用 Call
方法或 FavorFood
方法时,调用的是 Dog
类中定义的方法而不是 Animal
中定义的方法。
我们可以这样实例化 Dog
类型并调用相应的方法:
animal := Animal{"狗"}
dog := Dog{animal}
fmt.Println(dog.GetName(), "叫声:", dog.Call(), "喜爱的食物:", dog.FavorFood())
对应的打印结果如下:
狗 叫声: 汪汪汪 喜爱的食物: 骨头
与 PHP 或 Java 的继承机制不同,这种组合的方式更加灵活,我们不用考虑单继承还是多继承,你想要复用哪个类型的方法,直接组合进来就好了(需要注意组合的类型中包含同名方法,如果子类没有重写,调用的时候会报错),另外,我们可以通过任意调整被组合类型位置改变类的内存布局:
type Puppy struct {
Animal
Dog
}
和
type Puppy struct {
Dog
Animal
}
虽然功能一致,但是内存结构不同。需要注意的是,这种情况下,如果两个类型包含同名方法和属性并且 Puppy
中没有定义这些属性或重写对应的方法,直接在 Puppy
实例上调用的话,会因冲突而报错。
另外,在 Go 语言中,你还可以以指针方式继承某个类型的属性和方法:
type Dog struct {
*Animal
}
这种情况下,除了传入 animal
实例的时候要传入指针引用之外,其它调用无需修改:
animal := Animal{"狗"}
dog := Dog{&animal}
fmt.Println(dog.GetName(), "叫声:", dog.Call(), "喜爱的食物:", dog.FavorFood())
结构体是值类型,如果传入值字面量的话,实际上传入的是结构体值的副本,对内存耗费更大,所以传入指针性能更好。
最后,Go 语言没有类似 PHP 的 parent
关键字,我们可以把组合进来的类型当做子类的一个匿名字段,直接通过引用类型名调用父类被重写的方法或属性:
fmt.Println(dog.Animal.name, "叫声:", dog.Animal.Call(), "喜爱的食物:", dog.Animal.FavorFood())
也可以将其作为一个类型为其指定一个属性名称来调用对应的方法和属性:
type Dog struct {
name string
animal *Animal
}
...
fmt.Println(dog.animal.name, "叫声:", dog.animal.Call(), "喜爱的食物:", dog.animal.FavorFood())
3 Comments
type Animal struct { name string }
学院君,这个Animal.name属性,name应该改写为Animal.Name,Go首字母大写才外部可见。 否则,animal := Animal{"狗"}这个初始化会失败的
这里貌似没有让name在外部可见,调用的时候也是通过Animal.name来调用的
这个name应该算是animal结构体中的一个