第七章:继承与多态-对象村的优质生活

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

第七章:继承与多态-对象村的优质生活


规划程序时要考虑到未来:

    写出让他人能够很容易扩充的程序;
    本章会介绍5个更好的设计步骤、3个多态的技巧、8种让程序更有适应性的方法;
    还有4项对继承的建议;

第二章我们已经了解了继承的基本概念;
    以动物为例,老虎、狮子、狗等,实现具体的类之后:哪一个是子类、哪一个是父类;设计出继承结构之后,会有哪些方法需要被覆盖等;
    这就需要我们更深入的了解继承;

设计继承:

    把有共同程序的代码放在某给类中,然后告诉其他具体的类说此类是它们的父类;
    

类的成员:

    就是实例变量和方法;
    子类继承父类,会自动地继承父类的实例变量和方法(有些特殊情况);
    子类也可以加入自己的方法和实例变量,也可以覆盖掉继承自父类的方法;
        实例变量无法被覆盖是因为不需要,他们没有定义特殊的行为;

父类比较抽象,子类比较具体;

示例:(Code-Doctor.java)
public class Doctor{
    boolean worksAtHospital;
    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)完成类的继承层次;

使用继承来防止子类中出现重复的程序代码;

调用哪些方法?
    Animal:sleep();//动物
    Canine:roam();//犬科(继承 动物)
    Wolf:eat();//狼(继承 犬科)
那么:
    Wolf w = new Wolf();
    w.sleep();
    w.roam();
    w.eat();

当你调用对象引用的方法时,你会调用与该对象类型最接近的方法;
“最低阶”的意思是在层次树的最下方;
    JVM会从Wolf开始找,找不到继续往上找;
    编译器会保证引用特定的方法是一定能够被调用到;(在执行期间并不会在乎方法实际上是从哪个类找到的)
        如果某个类继承了一个方法,他就会有那个方法,方法在哪里定义对于编译器来说不重要;
        但在执行期间,JVM就是有办法找到正确的方法;这个“正确”的意思是最接近该类型的版本;

是一个 和 有一个:

    IS-A测试:继承关系判断;
        狼是一种动物;
    HAS-A关系:属性关系判断;
        动物有肢体,能活动;

IS-A测试适用在继承层次的任何地方;好的继承设计,所有子类都应该通过任一个上层父类的IS-A测试;
如果类Y是继承类X,且类Y是类Z的父类,那么Z应该能通过IS-A X的测试;

如何知道继承设计是对的?
    继承概念下的IS-A是个单向的关系;
    如果X IS-A Y合理,也有可能是两者相同,或 恰巧有相同的行为;
    还要记得X IS-A Y隐喻着X可以做出任何Y可以做的事情(可能还有更多的行为);这也对应了扩充的含义:扩充和延伸;

子类还可以不完全覆盖父类功能:
    public void roam(){
        super.roam();
    }
    可以通过super这个关键词来取用父类;这会先执行super版,再回来执行sub版定义的行为和功能;

如何知道子类能够继承哪些东西?
    子类可以继承父类的成员,包括实例变量和方法(稍后我们会提到其他继承的东西);
    父类可以通过存取权限决定子类是否能够继承某些特定的成员;

4中存取权限:(access level)

    左边是最受限的,越往右限制程度越小;
    private default protected public;
    存取权限控制了谁可以接触什么;

public:类型的成员会被继承;
private:类型的成员不会被继承;
(default和protected后续介绍)

任一类的成员包含了自定义的变量和方法以及从父类继承下来的任何东西;

不要滥用继承:

    继承虽然是面向对象设计的一项关键特性,但不一定是达成重用行为程序的最佳方式;
    不能单纯为了重用而使用继承,要考虑继承的合理性;(可以参考学习Head First 设计模式,接下来读这本书)
    子类需要时父类的一种特定类型,通过IS-A测试;

要点:

    子类extends父类出来的;
    子类会继承所有父类的public类型成员,但不会继承父类所有private类型的成员;
    继承下来的方法可以被覆盖,但实例变量不能被覆盖;
    使用IS-A测试继承机构的合理性;
    IS-A关系是单向的;
    当某个方法在子类中被覆盖过,调用这个方法时会调用覆盖过的版本;
    如果Y是extends X,且Y是类Z的父类,那么Z应该能通过IS-A X的测试;

继承的意义:

    通过提起出一组类间共同的抽象性,能将重复的代码放在父类中;如果需要修改,也只要改父类即可;
    修改之后,也只需重新编译,不许动子类;
    换上修改过的父类,扩充过的类都会自动使用到新的版本;
        Java程序是由一堆类组成,子类不需重新编译就能运用到新版本的父类(前提是父类没有破坏子类,后续会介绍);

1)避免了重复的程序代码;
2)定义出共同的协议;(按照OC的协议理解即可,协议就是一套接口,或者说一组方法)
    继承让你可以确保某个父类型之下的所有子类型都会有父类所持有的全部方法——即通过继承定义了相同类间的共同协议;
    Animal这个类拟出了所有动物子型的共同协议;(全部方法指的是全部可继承的方法)

多态(polymorphism):

    当你定义出一组类的父型时,你可以用子型的任何类来填补任何需要或期待父型的位置;
    即,通过父型类型的对象引用它的子型对象;

我们在声明和初始化引用类型时,重要的是引用类型与对象类型必须相符;
    Wolf wolf = new Wolf();//两者均为Wolf类型;
但在多态下,引用对象可以是不同类型:
    Animal myWolf = new Wolf();
    引用的是Animal,对象是Wolf;

运用多态时,引用类型可以是实际对象类型的父类:
    当你声明一个引用变量时,可以对该引用变量类型赋值可通过IS-A测试的对象;
    换句话说,任何extends过声明引用变量类型的对象都可以被赋值给这个引用变量(子类对象赋给父类引用);

由于多态的存在,在调用具体方法时,则会调用具体子类的方法(如果有的话);

参数和返回类型也可以多态:

    public void giveShot(Animal a){a.makeNoise();}
    调用的话可以是vobj.giveShot(new Dog())、vobj.giveShot(new Cat());
    执行a.makeNoise()的时候不管引用的对象到底是什么,该对象都会执行该方法;
    giveShot方法可以接收任何一种Animal;
    由于多态的存在,传入Dog则会执行Dog的makeNoise,传入Cat会执行Cat的makeNoise;

如果我们将程序代码编写成使用多态参数,也就是说将参数声明成父类类型,就可以在运行时传入任何的子类对象;

通过多态,就可以编写出引进新子类型时不必修改程序;

小知识:

继承树的层次少一点是合理的,大部分不会超过1~2层;
如果没办法看到类的源程序代码,又想要改变类的方法,可以通过继承的方式,在子类中覆盖该方法;
除了内部类(之后会介绍)之外,Java没有私有类这个概念,但可以通过3中方式防止类被继承:
    1)控制存取:类可以不能标记为私有,但是可以不标记共有,非公有类只能被同一个包的类做出子类;
    2)使用final修饰符:表示它是继承树的末端,不能被继承;
    3)让类只拥有private的构造程序(constructor,后续会介绍);
如果想要防止特定的方法被覆盖,可以将该方法标识为final;将整个类标识成final表示没有任何的方法可以被覆盖;

覆盖的规则:

    方法就是合约的标志;
    编译器会寻找类型来决定你是否可以调用该引用的特定方法;但在执行期间,JVM寻找的并不是引用所指的类型,而是堆上的对象;
    因此若编译器已经同意这个调用,则唯一能够通过的方法是 覆盖的方法也有相同的参数和返回类型;
1)参数必须要一样,且返回类型必须要兼容;(覆盖不能改变参数)
2)不能降低方法的存取权限;(不能讲共有变成私有)
    

方法的重载(overload):

    两个方法名相同,但参数不同,重载与多态毫无关系;
    重载可以有同一个方法的多个不同参数版本以方便调用;
    重载方法不是用来满足定义在父类的多态合约,所有重载的方法比较有扩展性;
    重载版的方法是不同的方法,与继承和多态无关;与覆盖的方法也不一样;
1)返回类型可以不同;
2)不能只改变返回类型;
3)可以更改存取权限;(可以任意的设定overload版method的存取权限)


猜你喜欢

转载自blog.csdn.net/baby_hua/article/details/79260020
今日推荐