今天继续打卡学习极客时间上的专栏“设计模式之美”, 本篇是专栏第9节的学习笔记,第10节是专栏中介绍"面向对象"这一设计原则和设计思想的一节。

笔记

组合优于继承,多用组合少用继承是面向对象编程中非常经典的一条设计原则。

继承虽然是面向对象的四大特性之一,使用继承可以解决代码复用的问题,但也有其缺点: 继承层次过深、过于复杂会影响到代码的可维护性

因此继承这个特性有很大争议,不应过度使用。因此,Go语言中也是不直接提供继承特性而提倡使用组合。

继承是一种is-a的关系,而组合是一种has-a关系。

实际中组合(Composition)往往结合接口、委托(delegation)这两种技术手段来解决上述继承的问题。专栏中使用Java给的例子如下:

 1
 2public interface Flyable {
 3  void fly()
 4}
 5public class FlyAbility implements Flyable {
 6  @Override
 7  public void fly() { //... }
 8}
 9//省略Tweetable/TweetAbility/EggLayable/EggLayAbility
10
11public class Ostrich implements Tweetable, EggLayable {//鸵鸟
12  private TweetAbility tweetAbility = new TweetAbility(); //组合
13  private EggLayAbility eggLayAbility = new EggLayAbility(); //组合
14  //... 省略其他属性和方法...
15  @Override
16  public void tweet() {
17    tweetAbility.tweet(); // 委托
18  }
19  @Override
20  public void layEgg() {
21    eggLayAbility.layEgg(); // 委托
22  }
23}

实际中我们如何判断该使用组合还是继承? 组合优于继承,但组合也不是完美的。如果类之间的继承结构稳定(不会轻易改变),继承层次较浅(如最多两层),继承关系不复杂,也可以使用继承。后边要学习的一些设计模式会固定使用组合或继承。例如,装饰模式(decorator pattern)、策略模式(strategy pattern)、组合模式(composite pattern)等都使用了组合关系,而模板模式(template pattern)使用了继承关系。

Go语言中的组合设计

学完本篇,我们来一起看一下Go语言中的组合设计。Go推崇组合设计的理念,Go语言中的结构体类型struct可以嵌套声明另外一个类型(普通类型或结构体struct)作为其成员,这种嵌套有两种方式:

  • 具名嵌套: 这种方式就是Go语言中的组合设计
  • 匿名嵌套: 这种方式"可以认为是Go语言对继承的支持"(Go不直接提供"继承"这种语法特性),匿名嵌套的其实就是嵌套类型名称作为成员字段名称的具名嵌套

Go语言结构体嵌套声明

匿名嵌套语法声明使得外层结构体的对象可以直接调用内嵌成员的方法,而无需带成员名或类型名。

匿名嵌套的例子:

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7type Bird struct {
 8}
 9
10func (b *Bird) fly() error {
11	fmt.Println("I can fly.")
12	return nil
13}
14
15type Parrot struct {
16	Bird
17}
18
19func main() {
20	parrot := &Parrot{}
21	parrot.fly()
22}

外层类型成员名字可以覆盖内层类型成员名字,如下面Parrot类型自己定义的fly方法覆盖了内部类型Bird类型的fly方法。

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7type Bird struct {
 8}
 9
10func (b *Bird) fly() error {
11	fmt.Println("I can fly.")
12	return nil
13}
14
15type Parrot struct {
16	Bird
17}
18
19func (p *Parrot) fly() error {
20	fmt.Println("parrot fly.")
21	return nil
22}
23
24func main() {
25	parrot := &Parrot{}
26	parrot.Bird.fly()
27	parrot.fly()
28}

使用Go重写代码示例

下面使用Go重写专栏中用Java展示的代码示例:

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7type Flyable interface {
 8	fly() error
 9}
10
11type FlyAbility struct {
12}
13
14func (b *FlyAbility) fly() error {
15	fmt.Println("I can fly.")
16	return nil
17}
18
19//省略 Tweetable/TweetAbility/EggLayable/EggLayAbility
20
21type Ostrich struct {
22	FlyAbility
23	// TweetAbility
24	// EggLayAbility
25}
26
27func main() {
28	var flyable Flyable = &Ostrich{}
29	flyable.fly()
30}

可以看出Go对组合的支持比Java更加优雅。

参考