设计模式之继承和组合 2022-03-07
设计模式
设计模式永不过时,是沉淀下来可以反复使用以解决某个细小抽象问题的设计方法
例如工厂模式解决相似对象的初始化问题,观察者模式解决主题与订阅者之间的问题
若干设计模式的组合可以构成一种框架设计
继承和组合是设计模式的起源
- 继承指可以让某个类型(子类)的对象获得另一种类型(父类)对象的属性和方法,实现代码复用
- 多态机制使内部结构不同的对象可以共享相同的外部接口。即子类可以重写父类的某个函数,从而为这个函数提供不同于父类的行为。一个父类的多个子类可以为同一个函数提供不同的实现,从而在父类这个公共的接口下,表现出多种行为
当使用继承的时候,肯定是需要利用多态的特性,用父类的类型去引用具体子类的对象。如果用不到多态的特性,那么可以认为继承的关系是无用的
ps: c++中多态必须满足3个条件
①必须是公有继承
②必须是通过基类的指针或引用 指向派生类对象 访问派生类方法
③基类的方法必须是虚函数,且子类完成了虚函数的重写
继承存在的一些问题:
- 某些子类如果不需要支持父类的某些方法,但继承的机制让该子类强行把这些方法继承下来,子类只能通过重写这些方法(例如重写该方法什么都不做)达到注释掉的效果
- 基类一改,会作用到全部子类,耦合度极高,反而牵一发动全身
而组合的优点在于, 对于新发现的问题, 你总可以不需要修改历史代码, 轻而易举地组合出符合人脑认知的模型,就像拼装零件搭积木一样自然
具体地,如果某些方法不是类独有,那么可以把这些方法抽象成接口,再在类的内部按需进行自由组合
例如,我们要写一个老虎类(基类),可以派生出华南虎、东北虎等等子类。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就可以