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

笔记

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

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

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public interface Flyable {
  void fly()
}
public class FlyAbility implements Flyable {
  @Override
  public void fly() { //... }
}
//省略Tweetable/TweetAbility/EggLayable/EggLayAbility

public class Ostrich implements Tweetable, EggLayable {//鸵鸟
  private TweetAbility tweetAbility = new TweetAbility(); //组合
  private EggLayAbility eggLayAbility = new EggLayAbility(); //组合
  //... 省略其他属性和方法...
  @Override
  public void tweet() {
    tweetAbility.tweet(); // 委托
  }
  @Override
  public void layEgg() {
    eggLayAbility.layEgg(); // 委托
  }
}

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

Go语言中的组合设计

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

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

Go语言结构体嵌套声明

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

匿名嵌套的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
	"fmt"
)

type Bird struct {
}

func (b *Bird) fly() error {
	fmt.Println("I can fly.")
	return nil
}

type Parrot struct {
	Bird
}

func main() {
	parrot := &Parrot{}
	parrot.fly()
}

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
	"fmt"
)

type Bird struct {
}

func (b *Bird) fly() error {
	fmt.Println("I can fly.")
	return nil
}

type Parrot struct {
	Bird
}

func (p *Parrot) fly() error {
	fmt.Println("parrot fly.")
	return nil
}

func main() {
	parrot := &Parrot{}
	parrot.Bird.fly()
	parrot.fly()
}

使用Go重写代码示例

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
	"fmt"
)

type Flyable interface {
	fly() error
}

type FlyAbility struct {
}

func (b *FlyAbility) fly() error {
	fmt.Println("I can fly.")
	return nil
}

//省略 Tweetable/TweetAbility/EggLayable/EggLayAbility

type Ostrich struct {
	FlyAbility
	// TweetAbility
	// EggLayAbility
}

func main() {
	var flyable Flyable = &Ostrich{}
	flyable.fly()
}

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

参考