[译]Java中的继承 VS 组合

(文章翻译自Inheritance vs. Composition in Java)
这篇文章阐述了Java中继承和组合的概念。它首先给出了一个继承的例子然后指出怎么通过组合来提高继承的设计。最后总结出怎么在两者中作出选择。

1.继承

现在假设我们有一个Insect类。这个类包含了两个方法:1) move() and 2) 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");
	}
}

现在你想定义一个Insect类型的Bee类,但是对于方法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();
	}
}

类的继承图就像下面这么简单:
inheritance-vs-composition-1

输出:

Fly
Fly
Attack

Fly被打印出了两次,这就表明move()方法被调用了两次。但是它只应该被调用一次。这个问题是因为super.attack()方法引起的。Insect的attack()方法调用了move()方法。当一个子类调用super.attack()方法的时候,它也会调用覆盖的move()方法。

为了修复这个问题,我们可以:
1.消除子类的attack()方法。这个将使子类依赖超类的attack()的实现。如果在超类中的attack()方法在后来被改变了,例如超类的attack()方法使用另外一个方法去移动,那么子类也需要去改变了。这是一个不好的设计。

2.像下面这样重写attack()方法:

public void attack() {
	move();
	System.out.println("Attack");
}

这将会保证结果的正确性,因为子类将不再依赖超类了。但是,代码重复了超类,这个不符合软件工程中复用的规则。

这个继承设计是不好的,因为子类依赖超类的实现。如果超类改变了那么结果就会发生改变。

2.组合

组合可以代替继承在这个场景中使用。让我们来看下组合的解决方式。

attack()方法作为接口中一个抽象的方法。

interface Attack {
	public void move();
	public void attack();
}

不同种类的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);
	}
}

因为attack方法被提取出来了,Insect就不在和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;
	}
}

Beeb是Insect类型,它可以有attack方法>

// 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();
	}
}

类的结构图:
inheritance-vs-composition-2

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.什么时候应该选择哪种方法呢?

下面两个场景可以指导在继承和组合总作出选择:

1.如果是IS-A关系,而且一个类想把它的所有接口暴露给另外一个类,那么应该选择继承。

2.如果是HAS-A关系,那么应该选择组合。

总而言之,继承和组要都有他们的用途,需要了解他们相对的优势。

posted on 2015-01-09 23:31  叼烟斗的纤夫  阅读(454)  评论(0编辑  收藏  举报