组合(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(); } }
执行结果:
跑跑
我是一个小小小鸟,想要飞的更高
吃吃
【例子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); } }
执行结果:
Charge: 2 hours First Turn On: 3 hours Second Turn On: 3 hours No more usable, must charge!
我们通过组合来使用了电池对象所提供的功能,比如探测电量是否用尽(根据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)引用网友的一句很经典的话应该更能让大家分清继承和组合的区别:组合可以被说成“我请了个老头在我家里干活” ,继承则是“我父亲在家里帮我干活"。 |