Java015复用类之组合、继承和代理
什么是组合?
什么是继承?
在组合与继承之间选择
选择组合的情况
选择继承的情况
再论组合与继承
什么是代理?
Java015复用类之组合、继承和代理
什么是组合?
在新的类中产生现有类的对象。由于新的类是有现有类的对象所组成,所以这种方法被成为组合。组合只是复用了现有程序代码的功能,而非它的形式(接口)。现有对象是新的类的组成的一部分。
什么是继承?
按照现有类的类型来创建新类,无需改变现有类的形式(接口),采用现有类的形式(接口)并在其中添加新代码。继承并不只是复制基类的接口。当创建一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与用基类直接创建的对象是一样的。二者区别在于,后者来自于外部,而基类的子对象被包装在导出类对象内部。即创建导出类对象时Java会自动先创建好父类对象,有其父才有其子嘛。这一现象也说明了为何导出类构造器的最开始处必须要先调用父类构造器的原因(父类有默认构造器,Java会自动在导出类的构造器中插入对父类构造器的调用;否则,就需要手动显式调用父类有参构造器)。导出类和基类是同一种类型。
在组合与继承之间选择
选择组合的情况
组合和继承都允许在新的类中放置子对象,组合是显式地这样做,而继承则是隐式地做。那么怎样知道二者的区别在哪儿,以及怎样在二者之间做出选择呢?
组合技术通常用于想在新类中使用现有类的功能而非它的接口的这种情形。即,在新类中嵌入某个对象,让其实现所需要的功能,但新类的用户看到的只是为新类所定义的接口,而非所嵌入的对象的接口。
选择继承的情况
在继承的时候,使用某个现有类,并开发一个它的特殊版本。通常,这意味着你在使用一个通用类,并为了某种特殊需要而将其特殊化。
再论组合与继承
在面向对象编程中,生成和使用程序代码最有可能采用的方法就是直接将数据和方法包装进一个类中,并使用该类的对象。也可以运用组合技术使用现有类来开发新的类;而继承技术其实是不太常用的。因此,尽管在教授OOP的过程中我们多次强调继承,但这并不意味着要尽可能使用它。相反,应当慎用这一技术,其使用场合仅限于你确信使用该技术确实有效的情况下。到底是该用组合还是用继承,一个最清晰的判断办法就是问一问自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的;但如果不需要,则应当好好考虑自己是否需要继承。
什么是代理?
复用类的第三种方式被称为代理,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向前运动(forward()).更准确地讲,SpaceShip包含SpaceShipControls,与此同时,SpaceShipControls的所有方法在SpaceShip中都暴露了出来。代理解决了此难题:
public class SpaceShipDelegation{
private String name;
private SpaceShipControls controls =
new SpaceShipControls();
public SpaceShipDelegation(String name){
this.name = name;
}
//Delegated methods
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 static void main(String[] args){
SpaceShipDelegation protector =
new SpaceShipDelegation(“NSEA protector”);
protector .forward(100);
}
}
可以看到,上面的方法是如何传递给了底层的controls对象,而其接口由此也就与使用继承得到的接口相同了。但是,我们使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中的方法的某个子集。
尽管Java不直接支持代理,但是很多开发工具却支持代理。例如,使用JetBrains Idea IDE就可以自动生成上面的例子。