简单理解复合优先于继承
程序员文章站
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)
上一篇: [土味]自制TreeNode
下一篇: AtomicBoolean类的使用