继承与组合
来源:互联网 发布:手机如何修改表白源码 编辑:程序博客网 时间:2024/04/19 09:06
子类继承父类时,子类将可以从父类继承得到属性和方法,如果访问权限允许,子类将可以直接访问父类的属性和方法,相当于子类可以直接复用父类的属性和方法,确实非常方便。
继承带来高度复用的同时,也带来了一个严重的问题:继承严重地破坏了父类的封装性。封装性是指每个类都应该封装它内部信息和实现细节,而只暴露必要的方法给其他类使用。但在继承中,子类可以直接访问父类的属性和方法,从而造成了子类和父类的严重耦合。
为了保证父类良好的封装性,不会被子类随意改变,设计父类通常应该遵循规则:
1、尽量隐藏父类的内部数据,即把父类的所有属性都设置成private访问类型,不用让子类直接访问父类属性;
2、不要让子类可以随意访问、修改父类的方法。父类中哪些仅为辅助其他的工具方法,应该使用private修饰,让子类无法访问;如果父类中的方法需要被外部类调用,必须以public修饰,但又不希望子类重写该方法,可以使用final修饰该方法;如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,可以使用protected修饰该方法;
3、不用在父类构造器中调用被子类重写的方法。
例如:
class Base{
public Base(){
test();
}
public void test(){
System.out.println("将被子类重写的方法");
}
}
class sub extends Base {
private String name;
public void test(){
System.out.println("子类重写父类的方法,其name字符串长度"+name.length());
}
public static void main(String[] args){
Sub s=new Sub();//产生空指针异常
}
}
改程序运行结果会产生空指针异常。
解析:当系统试图创建Sub对象时,同样会先执行器父类的构造器,而Base构造器中调用了test(),这个方法不是父类中的,而是子类中重写的test()方法。此时,Sub对象的name属性时null,因此引发空指针异常。
4、如果想把某些类设置成最终类,即不能被当成父类,则可以使用final修饰该类。除此之外,或者使用private修饰这个类的所有构造器,从而保证子类方法无法调用该类的构造器,也就无法继承该类。对于把所有的构造器都使用private修饰的父类而言,可另外提供一个静态方法,用于创建该类的实例。
到底何时需要从父类派生新的子类呢?不仅需要保证子类是一种特殊的父类,而且还需要具备以下两个条件之一:
1、子类需要额外增加属性,而不仅仅是属性值的改变。
2、子类需要增加自己独有的行为方式(包括增加新的方法或重写父类方法)。
如果只出于类复用的目的,并不一定需要使用继承,完全可以使用组合来实现。而组合不会破坏封装的特性。
例如:假设有三个类:Animal、Wolf和Bird。使用继承定义如下:
class Animal{
private void beat(){
System.out.println("心脏跳动...");
}
public void breath(){
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
}
class Bird extends Animal{
public void fly(){
System.out.println("我在天空自在的飞翔...");
}
public void breath(){
System.out.println("我是鸟,我也呼吸");
}
}
class Wolf extends Animal{
public void run(){
System.out.println("我在陆地上的迅速奔跑...");
}
public void breath(){
System.out.println("我是狼,我也呼吸");
}
}
public class TestInherit {
public static void main(String[] args){
Bird b=new Bird();
b.breath();
b.fly();
Wolf w=new Wolf();
w.breath();
w.run();
}
}
使用组合定义如下:
class Animal{
private void beat(){
System.out.println("心脏跳动...");
}
public void breath(){
beat();
System.out.println("吸一口气,吐一口气,呼吸中...");
}
}
class Bird{
//将原来的父类嵌入原来的子类,作为子类的一个组合成分
private Animal a;
public Bird(Animal a){
this.a=a;
}
public void breath(){//重新定义一个自己的breath方法
a.breath();//直接服用Animal提供的breath方法来实现Bird的breath方法
}
public void fly(){
System.out.println("我在天空自在的飞翔...");
}
}
class Wolf{
// 将原来的父类嵌入原来的子类,作为子类的一个组合成分
private Animal a;
public Wolf(Animal a){
this.a=a;
}
public void breath(){//重新定义一个自己的breath方法
a.breath();//直接服用Animal提供的breath方法来实现Bird的breath方法
}
public void run(){
System.out.println("我在陆地上的迅速奔跑...");
}
}
public class TestComposite {
public static void main(String[] args){
//此时需要显式创建被嵌入的对象
Animal a1=new Animal();
Bird b=new Bird (a1);
b.breath();
b.fly();
Animal a2=new Animal();
Wolf w=new Wolf(a2);
w.breath();
w.run();
}
}
两部分代码运行结果相同。
不管是继承还是组合,都允许在新类中直接复用旧类的方法。
对于继承而言,子类可以直接获得父类的public方法,程序使用子类时,将可以直接访问该子类从父类那里继承得到的方法;而组合则是把旧类对象作为新类的属性嵌入,用以实现新类的功能,用户看到的是新类的方法,而不能看到嵌入对象的方法。因此,通常在新类里使用private修饰嵌入旧类对象。
注意:使用组合关系实现复用时,需要创建两个Animal对象,但这并不意味着使用组合关系时系统开销更大。原因是:当创建一个子类对象时,系统会隐式地为之创建对应的父类对象,这个父类对象可以在子类实例方法里通过super引用来访问。因此,当采用继承关系来实现复用时,系统创建子类对象时,系统会隐式创建与之对应的父类对象;采用组合关系来实现复用时,时程序员来手动创建被嵌入类的对象。其系统开销不会有本质的差别。
继承时对已有的类做一番改造,以此获得一个特殊的版本。即,就是讲一个较为抽象的类改造成能适应于某些特定需求的类。因此,对于Wolf和Animal的关系,使用继承更能表达其现实意义。而用一只动物来合成一匹狼毫无意义。如果两个类之间有明确的整体、部分关系,例如Person类需要复用Arm类的方法(Person对象由Arm对象组合而成),此时就应该采用组合关系来实现复用。
总之,继承要表达的是一种“是(is-a)”关系,而组合表达的是“有(has-a)关系。
- 论组合与继承
- 组合与继承
- 继承与组合
- 组合与继承
- 组合与继承
- 继承与组合
- 组合 继承 与 代理
- 组合与继承
- 组合与继承
- 继承与组合
- 继承(Virtual)与组合
- 继承与组合
- acm-继承与组合
- oj继承与组合
- ACM--继承与组合
- 14.5继承与组合
- oj继承与组合
- 继承与组合
- ASIHTTPRequest系列(三):文件上传
- python import 模块
- 视图和存储过程的区别
- hdu 4267 A Simple Problem with Integers 线段树&树状数组
- java 错误
- 继承与组合
- 从ADS1.2 到RV MDK移植
- 20130725 js中的旋转
- android 文本框自动联想功能
- 安装版Tomcat GC设置
- AHK中定义变量的两种方式
- 使用adb安装apk命令格式
- 修改远程桌面端口号
- eclipse java.lang.OutOfMemoryError: Java heap space