• 让我们进入面向对象的世界(三)



    前言

    自从上节课我们开始进入面向对象的封装已经有一些日子了,接下来我们会进入对象村非常有意思的环节,就是继承与多态。为什么我们要去学习这些特性呢?因为这是面向对象非常的重要,还记得我们在第一次进入面向对象村举的第一个例子吗?你的老板要你和你的朋友几个不同的形状,来满足需求,这个时候我们就可以给你的朋友解释一下什么是继承了。


    一.了解什么是继承

    在设计类的时候,你会把共同的带啊放在某个类中,然后告诉其他的类说此类是它们的父类。当某一个类继承另一个类的时候,也就是子类继承父类的时候。
    以java的方式来说,这是“子类继承父类“。继承关系意味着子类继承了父类的方法,当我们提及“类的成员”时,成员的意思就是实例变量和方法。


    说了这么多,我举一个简单的例子,让你们大致的了解一下,我上面说的一堆话,到底是什么。
    这是一个简单的继承的一个范例:

    public class Doctor {
        boolean worksAtHopsptial;
        void treatPatient(){
            //执行检查
        }
    }
    public class FamilyDoctor extends Doctor{
        boolean makesHouseCalls;
        void giveAdvice(){
            //提出诊断
        }
    }
    public class Surgeon extends Doctor{
        void treatPatient(){
            //执行检查
        }
        void makeIncision(){
            //截肢
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述
    这里面具体的例子就是,我们子类在继承父类的时候可以覆盖父类的方法,也可以增加子类自己独有的实例变量和方法,这这是一个简单例子,让大家继承的了解更清楚一些。


    二.我们针对继承来设计一个动物继承树

    在开始设计动物继承树之前,我们先来了解一下java继承的语法,在Java中如果要表示类之间的继承关系,需要extends关键字去来实现继承关系。具体怎么操作,跟着我去完成照这个动物继承树的例子之后,你就明白了。


    前言

    1.假设你要设计一个仿真系统程序,可以让用户设定将一群动物丢到某种环境中以观察会发生什么事情。现在不需要写出程序,我们只在乎设计。
    2.我们已经被告知一部分会用到的动物,但是并不知道还有多少种动物会加进来。每个动物都会用一个对象来表示,且动物会在环境中活动,执行任何被设计出的行为。
    3.这个程序必须能够在任何时间加入新类型的动物

    2.1 第一步 找出共同属性和行为的对象

    我这里列举六个动物
    在这里插入图片描述大家想一想着六种动物有什么共同点在里面?
    当然我已经给大家列出来了。我的答案并不是标准的。
    我么可以设五个实例变量
    picture:动物图像的名称
    food:动物所吃得食物。现在持有meat和grass俩种值
    hunger:代表饥饿程度的值,根据动物吃了多少而改变。
    boundaries:动物活动的区域的长宽
    location:动物活动区域的X与Y坐标。
    还有四个方法:
    makeNoise():动物发出的声音
    eat():动物吃的行为
    sleep():不同的动物的睡觉行为
    roam():其他行为


    2.2 设计代表共同状态行为的类

    因为对象都是动物的原因,我们可以用Animal作为共同父类的名称。
    并且构造图如下。
    在这里插入图片描述


    2.3 决定子类是否让某项行为有不同的运作方式。

    这里就非常有趣了。我们想一想我们应该让子类覆盖哪些方法呢?其实按照常识来说,狮子的叫声绝对不会跟河马的叫声一样,如果你说一样,那我也没办法了,所以我们要根据不同的类型去设计不同的行为程序。
    观察animal的这个类以后,我们认为eat()与makeNosie()应该由各个子类自行覆盖。


    2.4我们仔细去观察一下子类的特征,争取更多的抽象化的机会。

    类的继承结构已经大致成型。我们让每个子类都去覆盖掉makeNoise()与eat()这两个方法,因此狗不会瞄瞄叫、河马也不会抢狮子的食物。
    但或许我们还能做更多的设计。我们必须观察Animal的子类找寻是否有可以组织归纳使用共同程序代码的部分。看起来小红帽的好朋友大野狼跟狗有共同的部分。猫、狮子与老虎也有共同的部分。
    1.我们观察到Wolf与Dog可能有某些共同的行为,在Lion、Tiger、Cat之间也是。
    2.相信你应该知道我们说的是什么,因为动物本来就有组织化的层次(界、门、纲、目、科、属、种),我们可以用这些层次来制作有意义的类设计。我们使用犬科和猫科动物的分类来作出Feline与Caninc这两个类。
    3.我们决定Canine使用共同的roam()方法,因为它们都以相同的方式移动。Feline之间也是差不多。而Hippo则持续使用继承下来的roam()方法。

    于是我们得出了下面的继承图
    在这里插入图片描述

    上述我们对动物继承类的构造已经完成了。大家应该明白的差不多了吧。


    三.继承的相关语法

    前言

    3.1 父类的成员的访问

    1.子类和父类不存在相同的成员变量

    public class Base {
    int a;
    int b;
    }
    public class Derived extends Base{
    int c;
    public void method(){
    a = 10; // 访问从父类中继承下来的a
    b = 20; // 访问从父类中继承下来的b
    c = 30; // 访问子类自己的c
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.子类和父类的成员变量相同

    public class Base {
    int a;
    int b;
    int c;
    }
    /
    public class Derived extends Base{
    int a; // 与父类中成员a同名,且类型相同
    char b; // 与父类中成员b同名,但类型不同
    public void method(){
    a = 100; // 访问父类继承的a,还是子类自己新增的a?
    b = 101; // 访问父类继承的b,还是子类自己新增的b?
    c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
    // d = 103; // 编译失败,因为父类和子类都没有定义成员变量b
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    总结:
    如果访问的成员变量子类中有,优先访问自己的成员变量。
    如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
    如果访问的成员变量与父类中成员变量同名,则优先访问自己的。


    3.2 子类访问父类的成员方法

    1.成员方法名字不同

    public class Base {
    public void methodA(){
    System.out.println("Base中的methodA()");
    }
    }
    public class Derived extends Base{
    public void methodB(){
    System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
    methodB(); // 访问子类自己的methodB()
    methodA(); // 访问父类继承的methodA()
    // methodD(); // 编译失败,在整个继承体系中没有发现方法methodD()
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1. 成员方法名字相同
    public class Base {
    public void methodA(){
    System.out.println("Base中的methodA()");
    }
    public void methodB(){
    System.out.println("Base中的methodB()");
    }
    }
    public class Derived extends Base{
    public void methodA(int a) {
    System.out.println("Derived中的method(int)方法");
    }
    public void methodB(){
    System.out.println("Derived中的methodB()方法");
    }
    public void methodC(){
    methodA(); // 没有传参,访问父类中的methodA()
    methodA(20); // 传递int参数,访问子类中的methodA(int)
    methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
    }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    总结:
    通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到
    则访问,否则编译报错。
    通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用
    方法适传递的参数选择合适的方法访问,如果没有则报错;


    3.3 super关键字

    由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成
    员时,该如何操作?直接访问是无法做到的,Java提供了super关键字,该关键字主要作用:在子类方法中访问父
    类的成员。

    public class Base {
        int a;
        int b;
        public void methodA(){
            System.out.println("Base中的methodA()");
        }
        public void methodB(){
            System.out.println("Base中的methodB()");
        }
    }
    
    public class Derived extends Base{
        int a; // 与父类中成员变量同名且类型相同
        char b; // 与父类中成员变量同名但类型不同
        // 与父类中methodA()构成重载
        public void methodA(int a) {
            System.out.println("Derived中的method()方法");
        }
        // 与基类中methodB()构成重写(即原型一致,重写后序详细介绍)
        public void methodB(){
            System.out.println("Derived中的methodB()方法");
        }
        public void methodC(){
    // 对于同名的成员变量,直接访问时,访问的都是子类的
            a = 100; // 等价于: this.a = 100;
            b = 101; // 等价于: this.b = 101;
    // 注意:this是当前对象的引用
    // 访问父类的成员变量时,需要借助super关键字
    // super是获取到子类对象中从基类继承下来的部分
            super.a = 200;
            super.b = 201;
    // 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
            methodA(); // 没有传参,访问父类中的methodA()
            methodA(20); // 传递int参数,访问子类中的methodA(int)
    // 如果在子类中要访问重写的基类方法,则需要借助super关键字
            methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
            super.methodB(); // 访问基类的methodB()
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    四.怎么去检验继承关系

    这里其实我们当用一个类去继承另外一个类的时候,我们会去说这是子类应该去继承父类,若你想知道某物是否会去继承父类的时候,则可以用IS-A去验证。
    三角形是一个多边形?
    外科医生是一个医生?
    狮子是一种动物?
    这些都是继承关系。
    要确认你的设计是否正确,使用这样的测验来加以检验。如果不合理,则表示你的设计有问题。
    就比如我们洗澡的时候,浴室和澡盆有关联吧,确实是有关联,但不是继承上的关系。澡盆和浴室发生的是HAS-A的关系,如果表示浴室有澡盆的话,只能说明澡盆是浴室的实例变量,浴室里面会有一个澡盆的引用,但他们其实没有继承关系。

    这一个环节到这里就结束了,哈哈,朋友,简单的对我们的继承做个总结吧。


    五.总结

    我们通过一个问题来总结,我们对继承的意义,这里你先别说,听我列举,然后你再去思考

    1.避免了重复的代码
    在单一的位置定义共同程序代码,然后让子类继承父类的程序代码。当你想要改变这个行为程序时,只需修改这个地方,而子类就会发生同样的改变。
    2.定出了共同的协议
    当你在父类中定义方法时,它们会被子类继承,这样你就是在对其他程序代码声明:“我所有的子类(例如说subclass)都能用这些方法来执行这几项工作……”。也就是说你拟出了一份“合约”。
    我们提出动物继承关系的Animal这个类拟出所有动物子型的共同协议。

    朋友们,今天到这里就结束了,我们还没有讨论最精彩的部分,就是多态,这里留到下一部分,我们再讨论。

  • 相关阅读:
    【HCIP】RSTP
    C++中的菱形继承问题及解决方案
    nginx升级
    kafka配置
    Mysql数据重复问题处理
    苏州大学:从PostgreSQL到TDengine
    Html 后端了解基础
    Spark Structured Streaming - 1
    【ChatOCR】OCR+LLM定制化关键信息抽取(附开源大语言模型汇总整理)
    蓝牙基带的基础
  • 原文地址:https://blog.csdn.net/qq_45726327/article/details/127991941