正如Java中所有事物一样,问题解决都是围绕类展开的。可以通过创建新类来复用代码,而不必再重头开始编写。可以使用别人业己开发并调试好的类。
第一种方法非常直观:只需在新的类中产生现有类的对象。由于新的类是由现有类的对象所组成,所以这种方法称为组合。该方法只是复用了现有程序代码的功能,而非它的形式。
第二种方法则更细致一些,它按照现有类的类型来创建新类。无需改变现有类的形式,采用现有类的形式并在其中添加新代码。这种方式称为继承。
7.1 组合语法
我们可以在一个新类的定义中使用其他对象。这就是组合(composition)。组合是在Java中实现程序复用(reusibility)的基本手段之一。
一个对象是另一个对象的数据成员。比如我们看之前提到的充电电筒的例子:
一个充电电筒中的电池、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();
}
我们可以增加一个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);
}
}/*Output:
Charge: 2 hours
First Turn On: 3 hours
Second Turn On: 3 hours
No more usable, must charge!
*/
7.2 继承语法
继承是所有OOP语言和Java语言不可或缺的组成部分。当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式地从Java的标准根类Object进行继承。
7.3 代理
第三种关系称为代理,Java并没有提供对它的直接支持。这是继承与组合的中庸之道,因为我们将一个对象成员置于所要构造的类中(就像组合),但与此同时我们在新类中暴露了该成员对象的所有方法(就像继承)。例如,太空船需要一个控制模块:
public class SpaceShipControls {
void up(int velocity) {}
void down(int velocity) {}
void left(int velocity) {}
void right(int velocity) {}
void forward(int velocity) {}
void back(int velocity) {}
void turboBoost() {}
}
构造太空船的一种方式是使用继承:
public class SpaceShip extends SpaceShipControls {
private String name;
public SpaceShip(String name) {this.name=name;}
public String toString { return name; }
public static void main(String[] args){
SpaceShip protector = new SpaceShip("NSEA Protector");
protector.forward(100);
}
}
然而,SpaceShip并非真正的SpaceShipControls类型,即便你可以“告诉” SpaceShip向前运动。更准确地讲,SpaceShip包含SpaceShipControls。下面是代理的解决方式:
public class SpaceShipDelegation {
private String name;
private SpaceShipControls controls =
new SpaceShipControls();
public SpaceShipDelegation(String name) {
this.name=name;
}
public void back(int velocity) {
controls.back(velocity);
}
public void down(int velocity) {
controls.down(velocity);
}
public void forward(int velocity) {
controls.forward(velocity);
}
public void left(int velocity) {
controls.left(velocity);
}
public void right(int velocity) {
controls.right(velocity);
}
public void up(int velocity) {
controls.up(velocity);
}
public void turboBoost() {
controls.turboBoost();
}
public static void main(String[] args){
SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector");
protecor.forward(100);
}
}
可以看到,上面的方法是如何传递给了底层的controls对象,而其接口由此也就与使用继承得到的接口相同了。
7.7 向上转型
“为新的类提供方法”并不是继承技术中最重要的方面,其最重要的方面是用来表现新类和基类之间的关系。这种关系可以用“新类是现有类的一种类型” 这句话加以概括。
7.7.2 再论组合与继承
在面向对象编程中,生成和使用程序代码最有可能采用的方法就是直接将数据和方法包装进一个类,并使用该类的对象。也可以运用组合技术使用现有类来开发新的类;而继承技术其实是不太常用的。到底是该用组合还是用继承,一个最清晰的判断办法就是问一问自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的;但如果不需要,则应当好好考虑自己是否需要继承。
参考:
-
《Java编程思想》