本文阐述了 Java 中继承和组合的概念。第一个示例演示了继承,然后展示了如何使用组合来改进继承设计。我们将在最后总结如何在它们之间进行选择。
1. 继承
假设我们有一个类Insect
(英语昆虫) 该类包含两个方法:1. move()
(来自英语 move)和 2. attack()
(来自英语 Attack)
class Insect {
private int size;
private String color;
public Insect(int size, String color) {
this.size = size;
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void move() {
System.out.println("Move");
}
public void attack() {
move(); //assuming an insect needs to move before attacking
System.out.println("Attack");
}
}
现在您想要定义一个类Bee
(英国蜜蜂),它是 类型之一Insect
,但具有attack()
和 的不同实现move()
。这可以使用继承来完成:
class Bee extends Insect {
public Bee(int size, String color) {
super(size, color);
}
public void move() {
System.out.println("Fly");
}
public void attack() {
move();
super.attack();
}
}
public class InheritanceVSComposition {
public static void main(String[] args) {
Insect i = new Bee(1, "red");
i.attack();
}
}
类层次结构图很简单: 执行结果:
Fly
Fly
Attack
“Fly”被输入两次,因此该方法move()
被调用两次。但它应该只被调用一次。该问题是由 引起的super.attack()
。该方法attack ()
调用move()
类方法Insect
。当子类调用时super.attack ()
,它也会调用重写的方法move()
。为了解决这个问题,我们可以:
- 消除
attack()
子类方法。这将使子类依赖于attack()
超类的方法实现。如果attack()attack()
超类开始使用不同的移动方法,则子类也需要更改。这是不好的封装。 -
重写该方法
attack()
如下:public void attack() { move(); System.out.println("Attack"); }
-
这保证了正确的结果,因为子类不再依赖于超类。然而,该代码是超类的重复。(该方法
attack()
做的事情比仅仅输出字符串更复杂)。这不是好的软件设计,不应该有重复的代码。
2. 组成
您可以使用组合而不是继承。让我们看一下使用它的解决方案。函数attack()
被抽象为接口。
interface Attack {
public void move();
public void attack();
}
可以通过实现Attack接口来定义不同类型的攻击。
class AttackImpl implements Attack {
private String move;
private String attack;
public AttackImpl(String move, String attack) {
this.move = move;
this.attack = attack;
}
@Override
public void move() {
System.out.println(move);
}
@Override
public void attack() {
move();
System.out.println(attack);
}
}
由于攻击函数是外部的,因此该类Insect
不再包含它。
class Insect {
private int size;
private String color;
public Insect(int size, String color) {
this.size = size;
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
Class Bee
(来自英语Bee),该类型如何Insect
攻击。
// This wrapper class wrap an Attack object
class Bee extends Insect implements Attack {
private Attack attack;
public Bee(int size, String color, Attack attack) {
super(size, color);
this.attack = attack;
}
public void move() {
attack.move();
}
public void attack() {
attack.attack();
}
}
类图:
public class InheritanceVSComposition2 {
public static void main(String[] args) {
Bee a = new Bee(1, "black", new AttackImpl("fly", "move"));
a.attack();
// if you need another implementation of move()
// there is no need to change Insect, we can quickly use new method to attack
Bee b = new Bee(1, "black", new AttackImpl("fly", "sting"));
b.attack();
}
}
执行结果:
fly
move
fly
sting
3. 何时使用这些方法?
以下两点可以帮助您在继承和组合之间做出选择:- 如果您正在处理“IS”形式的类之间的关系,并且一个类想要向另一个类提供其所有接口,那么继承是更好的选择。
- 如果关系是“HAS”,则首选组合。
- 布洛赫、约书亚. 有效的java。印度培生教育,2008 年
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one-should...
GO TO FULL VERSION