为什么强烈不建议使用继承
我正在参与「启航计划」

这两天有空的时候看了下 承继和复合怎么挑选这个知识点,其实之前开发的时候遇到相似的问题我是无脑承继的,也没有考虑这么多,由于这些新增的父子类,都是在包内运用,并且父子类基本都是咱们同一个开发人员,所以一般不会有什么意外状况。

但是假如咱们要开发新的类,这个类需要对外开放,有很多模块会来承继咱们的类(或许咱们会承继第三方供给的公共类),这个时候就需要很小心的规划了,假如小伙伴们觉得以后不会接到这样的需求,其实就不必持续看的,下面内容仍是有点单调无聊的。

学习的内容


1. 承继(Inheritance)是什么

承继其实不必过多的去解说,由于我们都是十分熟悉的,它和封装(encapsulation)笼统(abstraction)多态(polymorphism) 组成面向对象编程的(Object-Oriented Programming)主要特征。

为什么强烈不建议使用继承

  • 代码示例
//父类
public class Animal {
  //名称
  protected String name;
  //品种
  String species;
​
  protected String getName() {
    return name;
   }
  protected void setName(String name) {
    this.name = name;
   }
  protected String getSpecies() {
    return species;
   }
  protected void setSpecies(String species) {
    this.species = species;
   }
}
​
​
//子类
public class Birds extends Animal {
  //翅膀长度
  protected String wingSize;
​
  protected String getWingSize() {
    return wingSize;
   }
  protected void setWingSize(String wingSize) {
    this.wingSize = wingSize;
   }
}

总结:

承继的优点:

  1. 子类能够复用父类的代码,承继父类的特性,能够减少重复的代码量
  2. 父子类之前结构层次更加明晰

承继的缺陷:

  1. 父子类之间归于强耦合性,一旦父类改动(比方添加参数),很或许会影响到子类,这就导致代码变得软弱
  2. 假如子类新增一个办法,但是后续父类晋级之后,和子类的办法签名相同回来类型不同,这会导致子类编译失败
  3. 会破坏封装性
  4. 不能进行拜访操控

缺陷第三条的解说:

下面是新建了一个集成HashSet的类,主要意图是想计算这个实例总共添加过多少次元素

  • addAll:批量添加数据
  • add:单个数据添加

终究计算出来的结果是 4 ,仅仅由于 super.addAll(c) 终究会调用add办法,也就导致重复计数了。

呈现这种状况是由于咱们在编写子类逻辑时不清楚父类办法的实现细节,然后造成了过错,即使咱们把add中的addCount++ 删去,也同样不能保证父类的逻辑会不会变化,这样就会导致子类十分软弱且不可控,简略总结便是子类依靠了父类的实现细节,所以这便是为什么会说破坏了封装性。

封装性:将数据和行为结合,形成一个全体,运用者不必了解内部细节,只能经过对外供给的接口进行拜访

@Slf4j
public class DestroyInheritance<E> extends HashSet<E> {
  private int addCount = 0;
​
​
  @Override
  public boolean add(E o) {
    addCount++;
    return super.add(o);
   }
​
  @Override
  public boolean addAll(Collection<? extends E> c) {
    addCount += c.size();
    return super.addAll(c);
   }
​
  public int getAddCount() {
    return addCount;
   }
​
  public static void main(String[] args) {
    //测验类,运用addAll批量添加
    DestroyInheritance<String> item = new DestroyInheritance<>();
​
    String[] arr = new String[]{"s","a"};
    item.addAll(Arrays.asList(arr));
    log.info("count:{}",item.getAddCount());
   }
}

2.复合是什么

复合从字面意思上也是能够了解的,便是将多个实例的行为和特征组合成一个新的类,简略了解便是新增的这个类,它拥有其他一个或许多个类的特征,比方家用轿车有轮子、底盘、发动机等等组件组成,那车子这个类就包含了轮子类和底盘类这些特点。

为什么强烈不建议使用继承

看一下下面这段代码,Car是一个运用了复合的类,他包含了引擎类Engine和轮胎类Tyre,那为什么要这样写呢,我的主意是有下面几点:

  1. 在doSomething办法中,我无需全部承继引擎类或许轮胎类,只需要根据实际状况调用某些办法即可,减少了之前对父类的严重依靠,造成的耦合性影响
  2. 引擎类只需要供给单个公共的办法给Car类运用,不需要彻底暴露其内部细节,也不必忧虑会呈现相似addAll终究调用add的问题
  • 代码示例
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
​
/**
 * 轿车类 
 **/
@Slf4j
public class Car {
  //引擎类实例
  private final Engine engine;
    //轮胎类实例
  private final Tyre tyre;
​
  public Car(Engine engine,Tyre tyre) {
    this.engine = engine;
    this.tyre = tyre;
   }
​
  public void doSomething(){
    //自定义逻辑
    engine.setBrand("坏牌子轮胎");
   }
​
  public String getEngineBrand(){
    //回来轮胎名称
    return engine.getBrand();
   }
​
  public static void main(String[] args) {
    Engine engine = new Engine();
    engine.setBrand("好牌子引擎");
    engine.setPower("250");
​
    Tyre tyre = new Tyre();
    tyre.setBrand("好牌子轮胎");
    tyre.setSize("50cm");
​
    Car car = new Car(engine, tyre);
    car.doSomething();
    log.info("轮胎名称:{}",car.getEngineBrand());
   }
}
​
/**
 * 引擎类
 */
@Data
class Engine{
  private String brand;
​
  private String power;
}
/**
 * 轮胎类
 */
@Data
class Tyre{
  private String brand;
​
  private String size;
}
​

3.承继和复合怎么挑选

为什么强烈不建议使用继承

说了半天,那究竟是用复合仍是用承继呢,我觉得最重要的一点我觉得是要搞清楚类之间的联系,关于承继而言,它是 “is-a” 的联系,是对工作的一种比方:人是动物、华为mate60是手机,仅仅关于动物、手机这种是更为笼统的事物,人和华为是对其类的衍生。

复合则是 “has-a” 的联系,比方:健康的人有两只眼睛、家用轿车有四个轮子,关于这种状况而言,咱们就需要用到复合。

承继和复合并非是肯定的好与坏,而是咱们要结合实际状况,假如是is-a联系,只有当子类和父类十分清晰存在这种联系时,咱们能够运用承继,并且在代码规划时,一定要考虑日后或许呈现的承继问题及后续代码的晋级迭代,否则很或许呈现令人崩溃的后续问题;而假如某一个对象仅仅新增类中的一个特点时,咱们就要运用复合来解决问题。


总结

写这篇文章也查找了一些其他博主的文章,整体的感受便是国内干流博客上相关的文章并不多,我们好像并不关心这个点,搜到有几篇还都是直接抄书放上去(并且长得都一模一样),很是抑郁,最终仍是去跳出去看了几篇他人的文章,感觉仍是有很多点仍是得仔细揣摩揣摩,后边再持续学习学习。