面向对象编程(十一)——组合以及与继承的区别
组合(has-a 关系)
我们已经尝试去定义类。定义类,就是新建了一种类型(type)。有了类,我们接着构造相应类型的对象。更进一步,每个类型还应该有一个清晰的接口(interface),供用户使用。
我们可以在一个新类的定义中使用其他对象。这就是组合(composition)。组合是在Java中实现程序复用(reusibility)的基本手段之一。
组合:一个对象是另一个对象的数据成员。
【例子1 code】
package com.gta.testoop.inherit;
/**@Description: 测试组合
* @date 2016-2-1 上午10:30:57
*/
//一个源文件可以定义多个类
//动物Animal类
public class Animal2 {
String eye;
public void run(){
System.out.println("跑跑");
}
public void eat(){
System.out.println("吃吃");
}
}
//哺乳动物Mammal类
class Mammal2 {
Animal2 animal2;//作为Mammal2的一个属性
public void taiSheng(){
System.out.println("我是胎生");
}
}
class Bird {
Animal2 animal2=new Animal2() ;
public void run(){
animal2.run();
System.out.println("我是一个小小小鸟,想要飞的更高");
}
public void eggSheng(){
System.out.println("我是卵生");
}
}
测试类:
package com.gta.testoop.inherit;
/**@Description: 测试组合
* @date 2016-2-2 上午9:34:11
*/
public class TestComposition {
public static void main(String[] args) {
Bird b=new Bird();
b.run();
b.animal2.eat();
}
}
执行结果:
View Code
【例子2 code】充电筒例子;
一个充电电筒中的电池、LED灯、按钮…… 都可以是一个对象。我们可以定义一个Battery类来定义和产生电池对象。而在充电电筒的类定义中,可以用一个电池对象作为其数据成员,来代表电池部分的状态。
我们下面定义一个Battery类,并用power来表示其电量。一个Battery的可以充电(chargeBattery)和使用(useBattery)。我们在随后的Torch类定义中使用Battery类型的对象作为数据成员:
class Battery
{
public void chargeBattery(double p)
{
if (this.power < 1.) {
this.power = this.power + p;
}
}
public boolean useBattery(double p)
{
if (this.power >= p) {
this.power = this.power - p;
return true;
}
else {
this.power = 0.0;
return false;
}
}
private double power = 0.0;
}
class Torch
{
/**
* 10% power per hour use
* warning when out of power
*/
public void turnOn(int hours)
{
boolean usable;
usable = this.theBattery.useBattery( hours*0.1 );
if (usable != true) {
System.out.println("No more usable, must charge!");
}
}
/**
* 20% power per hour charge
*/
public void charge(int hours)
{
this.theBattery.chargeBattery( hours*0.2 );
}
/**
* composition
*/
private Battery theBattery = new Battery();
}
上面的new为theBattery对象分配内存,不可或缺。
我们定义Battery类。Torch类使用了一个Battery类型的对象(theBattery)来作为数据成员。在Torch的方法中,我们通过操纵theBattery对象的接口,来实现Battery类所提供的功能(functionality)。
我们说,一个Torch对象拥有(has-a)一个Battery对象。上述关系可以表示成:

has-a: 手电有电池 (注意上面的菱形连线)
通过组合,我们可以复用Battery相关的代码。假如我们还有其他使用Battery的类,比如手机,计算器,我们都可以将Battery对象组合进去。这样就不用为每个类单独编写相关功能了。
我们可以增加一个Test类,看看实际效果:
public class Test
{
public static void main(String[] args)
{
Torch aTorch = new Torch();
System.out.println("Charge: 2 hours");
aTorch.charge(2);
System.out.println("First Turn On: 3 hours");
aTorch.turnOn(3);
System.out.println("Second Turn On: 3 hours");
aTorch.turnOn(3);
}
}
执行结果:
View Code我们通过组合来使用了电池对象所提供的功能,比如探测电量是否用尽(根据useBattery()的返回值)。
继承(is-a) VS 组合(has-a)
【组合和继承的综合例子】:
要实现的目标:鸟(Bird)和狼(Wolf)都是动物(Animal),动物都有心跳(beat()),会呼吸(beat()),但是鸟会fly(fly()),狼会奔跑(run()),用java程序实现以上描述。
InheritTest.java 使用继承方式实现目标
CompositeTest.java 使用组合方式实现目标
1 //InheritTest.java 使用继承方式实现目标
2 class Animal{
3 private void beat(){
4 System.out.println("心脏跳动...");
5 }
6 public void breath(){
7 beat();
8 System.out.println("吸一口气,呼一口气,呼吸中...");
9 }
10 }
11 //继承Animal,直接复用父类的breath()方法
12 class Bird extends Animal{
13 //创建子类独有的方法fly()
14 public void fly(){
15 System.out.println("我是鸟,我在天空中自由的飞翔...");
16 }
17 }
18 //继承Animal,直接复用父类的breath()方法
19 class Wolf extends Animal{
20 //创建子类独有的方法run()
21 public void run(){
22 System.out.println("我是狼,我在草原上快速奔跑...");
23 }
24 }
25 public class InheritTest{
26 public static void main(String[] args){
27 //创建继承自Animal的Bird对象新实例b
28 Bird b=new Bird();
29 //新对象实例b可以breath()
30 b.breath();
31 //新对象实例b可以fly()
32 b.fly();
33 Wolf w=new Wolf();
34 w.breath();
35 w.run();
36 /*
37 ---------- 运行Java程序 ----------
38 心脏跳动...
39 吸一口气,呼一口气,呼吸中...
40 我是鸟,我在天空中自由的飞翔...
41 心脏跳动...
42 吸一口气,呼一口气,呼吸中...
43 我是狼,我在草原上快速奔跑...
44
45 输出完毕 (耗时 0 秒) - 正常终止
46 */
47 }
48 }
49
50 //CompositeTest.java 使用组合方式实现目标
51 class Animal{
52 private void beat(){
53 System.out.println("心脏跳动...");
54 }
55 public void breath(){
56 beat();
57 System.out.println("吸一口气,呼一口气,呼吸中...");
58 }
59 }
60 class Bird{
61 //定义一个Animal成员变量,以供组合之用
62 private Animal a;
63 //使用构造函数初始化成员变量
64 public Bird(Animal a){
65 this.a=a;
66 }
67 //通过调用成员变量的固有方法(a.breath())使新类具有相同的功能(breath())
68 public void breath(){
69 a.breath();
70 }
71 //为新类增加新的方法
72 public void fly(){
73 System.out.println("我是鸟,我在天空中自由的飞翔...");
74 }
75 }
76 class Wolf{
77 private Animal a;
78 public Wolf(Animal a){
79 this.a=a;
80 }
81 public void breath(){
82 a.breath();
83 }
84 public void run(){
85 System.out.println("我是狼,我在草原上快速奔跑...");
86 }
87 }
88 public class CompositeTest{
89 public static void main(String[] args){
90 //显式创建被组合的对象实例a1
91 Animal a1=new Animal();
92 //以a1为基础组合出新对象实例b
93 Bird b=new Bird(a1);
94 //新对象实例b可以breath()
95 b.breath();
96 //新对象实例b可以fly()
97 b.fly();
98 Animal a2=new Animal();
99 Wolf w=new Wolf(a2);
100 w.breath();
101 w.run();
102 /*
103 ---------- 运行Java程序 ----------
104 心脏跳动...
105 吸一口气,呼一口气,呼吸中...
106 我是鸟,我在天空中自由的飞翔...
107 心脏跳动...
108 吸一口气,呼一口气,呼吸中...
109 我是狼,我在草原上快速奔跑...
110
111 输出完毕 (耗时 0 秒) - 正常终止
112 */
113 }
114 }
总结:
继承和组合都可以实现代码的复用。
- "is-a"(是)关系使用继承!
- "has-a"(拥有)关系使用组合!
最后总结为以下几点:
| 1)组合(has-a)关系可以显式地获得被包含类(继承中称为父类)的对象,而继承(is-a)则是隐式地获得父类的对象,被包含类和父类对应,而组合外部类和子类对应。 |
|
2)组合关系在运行期决定,而继承关系在编译期就已经决定了。 |
| 3)组合是在组合类和被包含类之间的一种松耦合关系,而继承则是父类和子类之间的一种紧耦合关系。 |
| 4)当选择使用组合关系时,在组合类中包含了外部类的对象,组合类可以调用外部类必须的方法,而使用继承关系时,父类的所有方法和变量都被子类无条件继承,子类不能选择。 |
| 5)最重要的一点,使用继承关系时,可以实现类型的回溯,即用父类变量引用子类对象,这样便可以实现多态,而组合没有这个特性。 |
| 6)还有一点需要注意,如果你确定复用另外一个类的方法永远不需要改变时,应该使用组合,因为组合只是简单地复用被包含类的接口,而继承除了复用父类的接口外,它甚至还可以覆盖这些接口,修改父类接口的默认实现,这个特性是组合所不具有的。 |
| 7)从逻辑上看,组合最主要地体现的是一种整体和部分的思想,例如在电脑类是由内存类,CPU类,硬盘类等等组成的,而继承则体现的是一种可以回溯的父子关系,子类也是父类的一个对象。 |
| 8)这两者的区别主要体现在类的抽象阶段,在分析类之间的关系时就应该确定是采用组合还是采用继承。 |
|
9)引用网友的一句很经典的话应该更能让大家分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活"。 |


浙公网安备 33010602011771号