欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

简单理解复合优先于继承

程序员文章站 2022-06-07 19:51:38
...

在看effective java时,有一条[复合优先于继承]的代码一直理解不了,不知道复合是什么意思,复合和继承到底有什么区别.网上代码都是 HashSet 的那个例子,不是很好理解 所以重新写了一段代码,方便理解.

首先看一个简单的例子
父类 Animal 代码

package com.example.springboot01.instance;

public class Animal {
    protected void print() {
        print2();
    }
    protected void print2() {
        System.out.println("Animal.print2()");
    }
}

子类 Dog 代码

package com.example.springboot01.instance;

public class Dog extends Animal {
    @Override
    public void print() {
        System.out.println("Dog.print()");
        super.print();
    }
    @Override
    public void print2() {
        System.out.println("Dog.print2()");
    }
}

测试类代码

    @Test
    public void testDog() {
        Dog dog = new Dog();
        dog.print();
    }

打印结果

Dog.print()
Dog.print2()

结果分析

  • 打印结果的第一行,没有问题,关键是第二行,打印出来的不是 Animal.print2() 而是 Dog.print2().
  • 原因是整个过程中调用方法的对象主体其实是 Dog 对象
  • 不管是 测试类中 dog.print();方法的调用,或者是 Dog 类中 super.print(); 方法的调用,还是 Animal 类中的 print2(); 方法的调用,其调用主体都是测试类中的那个 new Dog() 对象.
  • 所以最终Animal类中的那个 print2(); 方法调用等价于 new Dog().print2(); 方法调用.
  • 那么这样就容易产生一个问题,假如 Animal 类是一个公共类,其他类继承了它,并重写了里面的 print(); 和 print2(); 方法,那么很有可能会出现上面的那种情况,用户只调用了父类 Animal 的 print(); 方法,但是最终结果却还调用了子类 Dog 的 print2(); 方法,如果用户不去看父类 Animal 源码的话,是不知道自己重写的 print2(); 方法也被调用了,这就给程序开发埋下了隐患.
  • 那么这个问题怎么解决呢,其实也简单,就是变更方法调用的对象主体,之前出现问题是因为 对象主体一直是 new Dog(); 对象,那我们只要在调用父类 Animal 的方法时,将主体对象变更为 new Animal(); 对象就行了.

具体代码 Dog类 修改

package com.example.springboot01.instance;

public class Dog extends Animal {
    @Override
    public void print() {
        System.out.println("Dog.print()");
        // 修改前
        //super.print();
        // 修改后
        new Animal().print();
    }
    @Override
    public void print2() {
        System.out.println("Dog.print2()");
    }
}

修改后打印结果

Dog.print()
Animal.print2()
  • 这样就改好了,如果要进一步优化代码的话, new Animal(); 对象也可以通过构造器注入,修改后的代码如下

子类 Cat 通过构造器注入父类对象

package com.example.springboot01.instance;

public class Cat extends Animal {

    private final Animal animal;
    
    public Cat(Animal animal) {
        this.animal = animal;
    }
    @Override
    public void print() {
        System.out.println("Cat.print()");
        animal.print();
    }
    @Override
    public void print2() {
        System.out.println("Cat.print2()");
    }
}

修改后测试类

    @Test
    public void testDog() {
        Cat cat = new Cat(new Animal());
        cat.print();
    }

打印结果

Cat.print()
Animal.print2()
  • 通过构造器注入父类对象的代码是不是和effective java里面的示例代码 ForwardingSet类 有点类似?
  • 原理就是这样的,通过修改调用方法的主体对象,来达到避免误调方法的目的.
  • 现在再去看书中源码就好理解了,最好自己上手去执行一下代码,理解更容易.
  • 至于复合的含义,还是没懂.贴上原文的定义吧
  • 不扩展现有的类,而是在新的类中增加一个私有域,它引用现有类的一个实例 这种设计被称为“复合”(combination)

参考资料
Effective Java中文版(原书第3版)

相关标签: 土味 设计模式