首页 > 其他分享 >组合优于继承的理解

组合优于继承的理解

时间:2022-12-29 11:45:33浏览次数:43  
标签:优于 组合 继承 刹车 实现 brake 父类 public

前言

首先,广为流传的“组合优于继承” 的说法是一种不严谨的翻译,其来源如下:(众多设计模式强调的两个个最核心原则《Design Patterns: Elements of Reusable Object-Oriented Software》)

1.Program to an interface, not an implementation. (面向接口编程,而不是具体的实现)
2.Favor object composition over class inheritance.(若某个场景的代码复用既可以通过类继承实现,也可以通过对象组合实现,尽量选择对象组合的方式)

第一个原则的好处非常明显:可以极大程度地减少子系统具体实现之间的相互依赖
第二个原则则不那么容易理解,下面展开叙述 。

对象组合与类继承的对比:

面向对象设计的过程中,两个最常用的技巧就是类继承对象组合,同一个场景下的代码复用,这两个技巧基本上都可以完成。 但是他们有如下的区别:

  • 通过继承实现的代码复用常常是一种“白盒复用”, 这里的白盒指的是可见性: 对于继承来说,父类的内部实现对于子类来说是不透明的(实现一个子类时, 你需要了解父类的实现细节, 以此决定是否需要重写某个方法)
  • 对象组合实现的代码复用则是一种“黑盒复用”: 对象的内部细节不可见,对象仅仅是以“黑盒”的方式出现(可以通过改变对象引用来改变其行为方式)

这里通过汽车的刹车逻辑进行说明, 对于汽车来说,存在多种不同的型号,我们会很自然的希望定义一个类Car来描述所有汽车通用的刹车行为brake(),然后通过某种方式(继承/组合)来为不同的型号的汽车提供不同的刹车行为。

一、继承式

  • 如果通过继承来实现,思路就是定义一个Car,实现不同子类CarModelA,CarModelB来重写父类的brake()方法以体现不同型号车的刹车行为区别。
    public abstract class Car {
        // 也可以将该方法设置成抽象方法, 强迫子类来实现该方法
        public void brake() {
          // 提供一个默认的刹车实现
        }
    }
    
    public class CarModelA extends Car {
        public void brake() {
          aStyleBrake();// A 风格的刹车行为
        }
    }
    
    public class CarModelB extends Car {
        public void brake() {
          bStyleBrake(); // B 风格的刹车行为
        }
    }

    上述的例子展现了如何通过继承来完成不同型号车辆刹车行为的变化。但是可以注意到,每一个型号的车的刹车行为是在编译时就确定好的 , 没有办法在运行时刻将CarModelB的刹车行为赋予CarModelA。

    二、组合式

    • 如果通过对象组合的实现方式,则需要为Car定义一个引用,该引用的类型是一个为刹车行为定义的抽象(可以是抽象类或接口类)。
    • public interface IBrakeBehavior {
          public void brake();
      }
      
      public class AStyleBrake implements IBrakeBehavior {
          public void brake() {
              aStyleBrake(); // A 风格的刹车行为
          }
      }
      
      public class BStyleBrake implements IBrakeBehavior {
          public void brake() {
              bStyleBrake(); // B 风格的刹车行为
          }
      }
      
      //通过给下面的类赋予 AStyleBrake 或 BStyleBrake 可以完成不同 Model 的刹车行为的切换 
      
      // 同理, 汽车其他的行为(如启动 launch) 也可以用类似的方法实现
      // 不同型号的汽车实现, 可以通过赋予不同风格的行为实例来 “组装” 出来的, 也就不需要为 Car 定义不同的子类了 
      public class Car{
          protected IBrakeBehavior brakeBehavior;
      
          public void brake() {
              brakeBehavior.brake();
          }
      
          public void setBrakeBehavior(final IBrakeBehavior brakeType) {
              this.brakeBehavior = brakeType;
          }
      }

      注意:上面的刹车行为不一定需要通过接口来实现,定义一个BrakeBehaviour的父类,然后再定义AStyleBrake,BStyleBrake来继承该类,实现不同的行为, 同样是组合方式的应用。所以不难发现, 当我们拿类继承组合在一起进行对比时, 并不是以实现方式中是否有用到类继承来区分的。

      我们真正关注的是行为的继承行为的组合 :需要变化的行为是通过继承后重写的方式实现,还是通过 赋予不同的行为实例 实现。

      三、继承与组合对比

      3.1类继承优点

      • 类之间的继承关系时在编译时刻静态地定义好的,因此使用起来也非常直观,毕竟继承是被编程语言本身所支持的功能。
      • 类继承也使得修改要重用的代码变得相对容易,因为可以仅仅重写要更改的父类方法。

      3.2类继承缺点:

      • 第一个缺点是伴随第一个优点而生的:无法在运行时改变继承了父类的子类行为(这一点在之前汽车的例子中已经进行了说明)。

      • 第二个缺点更严重:通过继承实现的代码复用,本质上把父类的内部实现细节暴露给了子类,子类的实现会和父类的实现紧密的绑定在一起,结果是父类实现的改动,会导致子类也必须得改变

      以之前的例子进行说明,如果是通过继承的方式来实现不同型号汽车的刹车行为变化,假设现在我们基于Car这个父类实现了10种不同型号的汽车CarModel( A, B, C, D, E, F, G,H ,I , J ),其中前5个型号( A、B、C、D、E) 都没有重写父类的刹车方法,直接使用了父类Car提供的默认方法,后5个型号均提供了自己独特的brake实现 。

      现假设,我们希望对Car中的brake方法进行升级改造,然而升级改造后的brake行为只适用于C,D,最早的两种型号A,B并不兼容升级后的刹车行为。这样,我们为了保证A,B依旧能正常工作,就不得不把旧的brake实现挪到A、B中,或者分别去升级C、D、E中的brake方法。

      3.3对象组合优点:

      • 对象的组合是在运行时通过对象之间获取引用关系决定的,所以对象组合要求不同的对象遵从对方所实现的接口来实现引用传递,这样反过来会要求更加用心设计的接口,以此支持你在使用一个对象时,可以把它和很多其他的对象组合在一起使用而不会出现问题。
      • 对象的组合由于是通过接口实现的,这样在复用的过程中不会打破其封装。任意一个对象都可以在运行时刻被替换成另外一个实现了相同接口且类型相同的对象,更重要的是,由于一个对象的实现是针对接口而编写的,具体实现之间的依赖会更少。
      • 对象组合的方式可以帮助你保持每个类的内聚性,让每个类专注实现一个任务。类的层次会保持的很小,不会增长到一种无法管理的恐怖数量。(这也是Java只支持单继承的原因

      3.4对象组合缺点

      • 不具备之前所罗列的类继承的优点。

标签:优于,组合,继承,刹车,实现,brake,父类,public
From: https://www.cnblogs.com/dawuge/p/17012085.html

相关文章

  • 第一百一十八篇: JavaScript 原型链式继承
    好家伙,好家伙,本篇为《JS高级程序设计》第八章“对象、类与面向对象编程”学习笔记 1.原型链原型链是JS实现"继承"的方案之一ECMA-262把原型链定义为ECMAScript的主要......
  • 父类中可继承方法在处理private的一个demo
     publicabstractclassAbstractParent{publicAbstractParent(){System.out.println("Hello,parent");}protectedvoidearnMoney(){pre......
  • 面试官:你说说 js 中实现继承有哪几种方法?
    前言面试官:“你说说JavaScript中实现继承有哪几种方法?”紧张的萌新:“额,class中用extends实现继承,然后...没了...”面试官:“...”······大家好,我是CoderBin,......
  • 极客编程python入门-多重继承
    多重继承通过多重继承,一个子类就可以同时获得多个父类的所有功能。Python自带的很多库也使用了MixIn。举个例子,Python自带了​​TCPServer​​和​​UDPServer​​这两类网......
  • 计组学习09——Combinational Logic 组合逻辑电路
    计组学习——CombinationalLogic组合逻辑电路SynchronousDigitalSystems同步数字系统Synchronous:所有的操纵都是由中央时钟协调类似于系统的心跳!Digital:......
  • ts13_继承
    (function(){//定义一个dog类classAnimal{name:string;age:number;constructor(name:string,age:number){......
  • leetcode-17. 电话号码的字母组合
    17.电话号码的字母组合给定一个仅包含数字2-9的字符串,返回所有它能表示的字母组合。答案可以按任意顺序返回。给出数字到字母的映射如下(与电话按键相同)。注意1不对应......
  • 继承
    方法的重写是指子类出现和父类一样的方法@override可以用来检验方法的重写父类的私有方法不能被重写子类的访问权限一定要比父类高,public>默认>私有java类中只支持单......
  • 代码随想录算法训练营Day24|77. 组合
    代码随想录算法训练营Day24|77.组合回溯基础回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案,常见的问题类型为:组合问题:N个数里面按一定规则找出k个数的集合切割......
  • 代码随想录算法训练营Day25|216. 组合总和 III、17. 电话号码的字母组合
    代码随想录算法训练营Day25|216.组合总和III、17.电话号码的字母组合216.组合总和III216.组合总和III与「77.组合」类似,但区别在于题干要求的变化:只使用数字1......