设计模式之继承和组合 2022-03-07

2022-03-07  本文已影响0人  9_SooHyun

设计模式

设计模式永不过时,是沉淀下来可以反复使用以解决某个细小抽象问题的设计方法
例如工厂模式解决相似对象的初始化问题,观察者模式解决主题与订阅者之间的问题
若干设计模式的组合可以构成一种框架设计

继承和组合是设计模式的起源

继承存在的一些问题:

  1. 某些子类如果不需要支持父类的某些方法,但继承的机制让该子类强行把这些方法继承下来,子类只能通过重写这些方法(例如重写该方法什么都不做)达到注释掉的效果
  2. 基类一改,会作用到全部子类,耦合度极高,反而牵一发动全身

而组合的优点在于, 对于新发现的问题, 你总可以不需要修改历史代码, 轻而易举地组合出符合人脑认知的模型,就像拼装零件搭积木一样自然

具体地,如果某些方法不是类独有,那么可以把这些方法抽象成接口,再在类的内部按需进行自由组合
例如,我们要写一个老虎类(基类),可以派生出华南虎、东北虎等等子类。run() roar()是基类实现的方法,华南虎、东北虎继承过去。如果未来的一天,华南虎不再会奔跑,东北虎不再会吼叫,而其他老虎种类不变,这时的方案是:
方案1.通过分别重写华南虎、东北虎的run() roar()(重写为什么都不做)达到目的
方案2.基类之后增加几个过渡类——不会奔跑虎、不会叫虎,然后东北虎和华南虎再继承这些过渡类。多几次之后,继承链会很长,各种方法的继承也随之复杂

实际上,奔跑和吼叫并不是老虎类独有的,狮子类、狗类、羚羊类也可以有。因此,run和roar本质上是可以独立于具体类而存在的:
我们把run抽象成接口interface RunBehavior,把roar抽象成接口interface RoarBehavior,什么老虎需要怎么奔跑怎么吼叫,由自己去实现。见下

// golang

// interface RunBehavior
type RunBehavior interface {
    run()
}

// interface RoarBehavior
type RoarBehavior interface {
    roar()
} 

// 1.---NoRunTiger---
type NoRunTiger struct {}

func (t *NoRunTiger) run() {
    fmt.Println("i can't run")
}

func (t *NoRunTiger) roar() {
    fmt.Println("roar")
}
// ---NoRunTiger---

// 2.---NoRoarTiger---
type NoRoarTiger struct {}

func (t *NoRoarTiger) run() {
    fmt.Println("run")
}

func (t *NoRoarTiger) roar() {
    fmt.Println("i can't roar")
}
// ---NoRoarTiger---

// 3.---CommonTiger---
type CommonTiger struct {}

func (t *CommonTiger) run() {
    fmt.Println("run")
}

func (t *CommonTiger) roar() {
    fmt.Println("roar")
}
// ---CommonTiger---

上面的CommonTiger、NoRoarTiger和NoRunTiger都实现了RunBehavior和RoarBehavior接口,即把RunBehavior和RoarBehavior组合了起来。但是,这又引入了新的问题:由于行为相近,CommonTiger、NoRoarTiger和NoRunTiger实现的run()和roar()产生了重复代码。为了减少这些重复代码,我们引入了委托(Delegation)

Delegation,就是对接口的公共实现进行了封装,【封装变化】

// golang

// interface RunBehavior
type RunBehavior interface {
    run()
}

// interface RoarBehavior
type RoarBehavior interface {
    roar()
} 

// 以下是4个委托
type CanRun struct {}
func (cr *CanRun) run() {
    fmt.Println("run")
}

type CannotRun struct{}
func (cnr *CannotRun) run() {
    fmt.Println("i can't run")
}

type CanRoar struct {}
func (cr *CanRoar) run() {
    fmt.Println("roar")
}

type CannotRoar struct{}
func (cnr *CannotRoar) roar() {
    fmt.Println("i can't roar")
}


// 1.---NoRunTiger---
type NoRunTiger struct {
    *CannotRun
    *CanRoar
}

// 2.---NoRoarTiger---
type NoRoarTiger struct {
    *CanRun
    *CannotRoar
}

// 3.---CommonTiger---
type CommonTiger struct {
    *CanRun
    *CanRoar
}

这样,不管后面什么老虎也好,狮子也好,只要他们有共同的run roar表现,对应类或者结构体直接has-Delegation就可以了,无需每个类都去实现一次接口。如果run roar表现某一天需要改变,那么只需要改变Delegation就可以

小结

1.封装变化

2.针对接口(interface)编程,而不是针对实现(implement)编程

3.多组合接口,少用继承。继承是“is-a"的关系;组合是”has-a"的关系,相当于是把共用的、可独立的部分提取到外部而不是像继承那样把公共的东西提取到父类中

上一篇下一篇

猜你喜欢

热点阅读