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

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

继承(Inheritance)是什么

  • 继承其实不用过多的去解释,因为大家都是非常熟悉的,它和封装(encapsulation)抽象(abstraction)多态(polymorphism) 组成面向对象编程的(Object-Oriented Programming)主要特征。

  • 为什么强烈不建议使用继承_第1张图片

  • 代码示例

  • //父类
    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;
        }
    }
    
  • 继承的优点:

  • 子类可以复用父类的代码,继承父类的特性,可以减少重复的代码量

  • 父子类之前结构层次更加清晰

  • 继承的缺点:

  • 父子类之间属于强耦合性,一旦父类改动(比如增加参数),很可能会影响到子类,这就导致代码变得脆弱

  • 如果子类新增一个方法,但是后续父类升级之后,和子类的方法签名相同返回类型不同,这会导致子类编译失败

  • 会破坏封装性

  • 不能进行访问控制

  • 缺点第三条的解释:

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

    • addAll:批量增加数据
    • add:单个数据增加
  • 最终统计出来的结果是 4 ,只是因为 super.addAll© 最终会调用add方法,也就导致重复计数了。

  • 出现这种情况是因为我们在编写子类逻辑时不清楚父类方法的实现细节,从而造成了错误,即使我们把add中的addCount++ 删除,也同样不能保证父类的逻辑会不会变动,这样就会导致子类非常脆弱且不可控,简单总结就是子类依赖了父类的实现细节,所以这就是为什么会说破坏了封装性。

  • 封装性:将数据和行为结合,形成一个整体,使用者不用了解内部细节,只能通过对外提供的接口进行访问

  • @Slf4j
    public class DestroyInheritance extends HashSet {
        private int addCount = 0;
    ​
    ​
        @Override
        public boolean add(E o) {
            addCount++;
            return super.add(o);
        }
    ​
        @Override
        public boolean addAll(Collection c) {
            addCount += c.size();
            return super.addAll(c);
        }
    ​
        public int getAddCount() {
            return addCount;
        }
    ​
        public static void main(String[] args) {
           //测试类,使用addAll批量增加
            DestroyInheritance 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关系,只有当子类和父类非常明确存在这种关系时,我们可以使用继承,并且在代码设计时,一定要考虑日后可能出现的继承问题及后续代码的升级迭代,不然很可能出现令人崩溃的后续问题;而如果某一个对象只是新增类中的一个属性时,我们就要使用复合来解决问题。

你可能感兴趣的:(JAVA基础工作中实际总结,编程学习,java,android,开发语言,微服务,面试)