• 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 秒) - 正常终止
  • 相关阅读:
    [C++11新特性] weak_ptr和unique_ptr
    [C++11新特性] shared_ptr共享的智能指针
    VS2019 Qt5.15.2 开发环境搭建
    【C++11 新特性】Lambda表达式(三)
    【C++11 新特性】bind(二)
    【C++11 新特性】function(一)
    【IPC 进程间通信】有名管道的简单实现
    【IPC 进程间通信】常用进程间通信方式总结
    Qt 文件常见操作管理类
    【GitHub 开源分享】QML 在线预览工具
  • 原文地址:https://www.cnblogs.com/weststar/p/12371426.html
Copyright © 2020-2023  润新知