4.8 继承与组合

继承是实现类复用的重要手段,但继承有一个大的坏处:破坏封装。相比之下,组合也是实现类复用的重要方式,且能提供更好的封装性。

一、使用继承的注意点

   子类扩展父类,子类可以从父类继承得到成员变量和方法,如果访问权限允许,子类可以访问父类的成员变量和方法,相当于可以直接复用父类的成员变量和方法。继承却严重破坏了父类的封装性。在继承关系中,子类可以直接访问父类的成员变量(内部信息)和方法,从而造成子类和父类严重耦合。从这个角度看,父类的实现细节对子类不再透明,子类可以访问父类的成员变量和方法,并可以改变父类的实现细节(例如:通过方法重写来改变父类的方法实现),从而导致子类可以恶意篡改父类方法。

  为了保证父类良好的封装性,不会被子类进行随意改变,设计父类通常应该遵循以下规则:
★尽量隐藏父类的内部数据。尽量把父类的所有成员变量都设置成private访问类型,不要让子类直接访问父类的成员变量。

★不要让子类可以随意访问、修改父类的方法。

  父类中那些仅为辅助其他的工具方法,应该使用private访问控制符修饰,让子类无法直接访问该方法;

  如果父类中的方法需要被外部类调用,则必须以public修饰,但又不希望子类重写该方法,可以使用final修饰符来修饰该方法;

  如果希望父类中的某个方法被子类重写,但又不希望被其他类自由访问,则可以使用protected来修饰该方法。

★尽量不要在父类构造器中调用要被子类重写的方法。

class  Base
{
    //在构造器中调用方法
    public Base()
    {
        test();
    }
    public void test()//1号test()方法
    {
        System.out.println("将被子类重写的方法");
    }
}

public class Sub extends Base
{
    private String name;
    public void test() //2号test()方法
    {
        System.out.println("子类重写父类的方法,"+"其name字符串长度"+name.length());
    }
    public static void main(String[] args)
    {
        //下面代码将会引发空指针异常
        var s=new Sub();
    }
}

运行结果:

---------- 运行Java捕获输出窗 ----------
Exception in thread "main" java.lang.NullPointerException
    at Sub.test(Sub.java:19)
    at Base.<init>(Sub.java:6)
    at Sub.<init>(Sub.java:14)
    at Sub.main(Sub.java:24)

输出完成 (耗时 0 秒) - 正常终止

  当系统试图创建Sub对象时,同样会先执行其父类的构造器,如果父类构造器调用了被其他子类重写的方法,则变成调用被子类重写后的方法。当创建Sub对象时,会先执行Base类中的Base构造器,而Base构造器中调用了test()方法——并不是1号test()方法,而是调用2号test()方法,此时Sub对象的name实例变量是null,因此将引发空指针异常。

何时需要从父类派生出新的子类?
★子类需要增加额外的成员变量,而并不仅仅是变量值的改变。

★子类需要增加自己独特的行为方式(包括增加新的方法或重写父类的方法)

二、利用组合实现复用

  对于继承而言,子类可以直接获得父类public方法,程序使用子类时将可以直接访问从父类那里继承到的方法;而组合则是把旧类对象作为新类成员变量组合起来,用于实现新类功能,用户看到的是新类的方法,而不能看到被组合对象的方法。因此通常需要在新类里使用private修饰被组合的旧类对象。

  仅从类复用的角度来看,父类的功能等同于被组合的类,都将自身的方法提供给新类使用;子类和组合关系里的整体类,都可以复用原有类的方法,用于实现自身的功能。

假设有三个类:Animal、Wolf、Bird,它们之间的继承树关系如图:

 1 class Animal 
 2 {
 3     private void beat()
 4     {
 5         System.out.println("心脏跳动...");
 6     }
 7     public void breathe()
 8     {
 9         beat();
10         System.out.println("吸气,吐气...");
11     }
12 }
13 
14 //继承Animal,直接复用父类的breathe()方法
15 class Bird extends Animal
16 {
17     public void fly()
18     {
19         System.out.println("我在天上自由飞翔...");
20     }
21 }
22 
23 //继承Animal,直接复用父类的breathe()方法
24 class Wolf extends Animal
25 {
26     public void run()
27     {
28         System.out.println("我在陆地上奔跑...");
29     }
30 }
31 
32 public class InheritTest
33 {
34     public static void main(String[] args)
35     {
36         var b=new Bird();
37         b.breathe();
38         b.fly();
39         var w=new Wolf();
40         w.breathe();
41         w.run();
42     }
43 }
44 ---------- 运行Java捕获输出窗 ----------
45 心脏跳动...
46 吸气,吐气...
47 我在天上自由飞翔...
48 心脏跳动...
49 吸气,吐气...
50 我在陆地上奔跑...
51 
52 输出完成 (耗时 0 秒) - 正常终止

上面程序可以改成以下方式也可以实现相同的复用。

 1 class Animal 
 2 {
 3     private void beat()
 4     {
 5         System.out.println("心脏跳动...");
 6     }
 7     public void breathe()
 8     {
 9         beat();
10         System.out.println("吸气呼气...");
11     }
12 }
13 
14 class Bird
15 {
16     //将原来的父类组合到子类,作为子类的一个组合部分
17     private Animal a;
18     public Bird(Animal a)
19     {
20         this.a=a;
21     }
22     //重新定义一个自己的breathe()方法
23     public void breathe()
24     {
25         a.breathe();//直接复用Animal提供的breathe()方法
26     }
27     public void fly()
28     {
29         System.out.println("鸟在天上飞");
30     }
31 }
32 
33 class Wolf
34 {
35     private Animal a;
36     public Wolf(Animal c)
37     {
38         this.a=c;
39     }
40     //重新定义一个自己的breathe()方法
41     public void breathe()
42     {
43         a.breathe();//直接复用Animal提供的breathe()方法
44     }
45     public void run()
46     {
47         System.out.println("狼在地上跑");
48     }
49 
50 }
51 
52 public class CompositionTest
53 {
54     public static void main(String[] args)
55     {
56         //显示创建被组合的对象
57         var a1=new Animal();
58         var b=new Bird(a1);
59         b.breathe();
60         b.fly();
61 
62         var a2=new Animal();
63         var w=new Wolf(a2);
64         w.breathe();
65         w.run();
66     }
67 }
68 ---------- 运行Java捕获输出窗 ----------
69 心脏跳动...
70 吸气呼气...
71 鸟在天上飞
72 心脏跳动...
73 吸气呼气...
74 狼在地上跑
75 
76 输出完成 (耗时 0 秒) - 正常终止

猜你喜欢

转载自www.cnblogs.com/weststar/p/12371426.html
4.8