美文网首页
设计模式之继承和组合 2022-03-07

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

作者: 9_SooHyun | 来源:发表于2022-03-07 17:29 被阅读0次

    设计模式

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

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

    • 继承指可以让某个类型(子类)的对象获得另一种类型(父类)对象的属性和方法,实现代码复用
    • 多态机制使内部结构不同的对象可以共享相同的外部接口。即子类可以重写父类的某个函数,从而为这个函数提供不同于父类的行为。一个父类的多个子类可以为同一个函数提供不同的实现,从而在父类这个公共的接口下,表现出多种行为
      当使用继承的时候,肯定是需要利用多态的特性,用父类的类型去引用具体子类的对象。如果用不到多态的特性,那么可以认为继承的关系是无用的
      ps: c++中多态必须满足3个条件
      ①必须是公有继承
      ②必须是通过基类的指针或引用 指向派生类对象 访问派生类方法
      ③基类的方法必须是虚函数,且子类完成了虚函数的重写

    继承存在的一些问题:

    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"的关系,相当于是把共用的、可独立的部分提取到外部而不是像继承那样把公共的东西提取到父类中

    相关文章

      网友评论

          本文标题:设计模式之继承和组合 2022-03-07

          本文链接:https://www.haomeiwen.com/subject/woynrrtx.html