继承与组合

来源:互联网 发布:手机如何修改表白源码 编辑:程序博客网 时间: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)关系。

 

原创粉丝点击