zoukankan      html  css  js  c++  java
  • 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 秒) - 正常终止
  • 相关阅读:
    状态压缩 + 暴力 HDOJ 4770 Lights Against Dudely
    简单几何(推公式) UVA 11646 Athletics Track
    简单几何(四边形形状) UVA 11800 Determine the Shape
    简单几何(求交点) UVA 11437 Triangle Fun
    计算几何模板
    简单几何(相对运动距离最值) UVA 11796 Dog Distance
    简单几何(求划分区域) LA 3263 That Nice Euler Circuit
    覆盖的面积 HDU
    Desert King 最小比率生成树 (好题)
    约会安排 (区间合并)毒瘤题
  • 原文地址:https://www.cnblogs.com/weststar/p/12371426.html
Copyright © 2011-2022 走看看