谈谈我对复用类的理解


      写此文的目的很简单,就是用来记录,最近Think in Java看到了第七章,随着进度的增加,问题也随之而来,经常出现明明记得在这本书里看到过相关的内容,但总是也找不到在哪看到的,只是有一个模糊的概念,我会在这里简短地记录第七章比较重要的知识和个人见解,以供以后查阅。
引:在Java中有两种方法可以实现复用代码,

第一种方法称为组合,只需在新的类中产生现有类的对象。
第二种方法称为继承,他按照现有类的类型来创建新类。使用extends关键字

它们都从属面向对象三个基本特征中的 继承 分支下:

面向对象基本特征

我们应该将【实现继承的继承】和【继承】这种概念区分开:

【实现继承的继承】是一种实现复用代码的方法,我们下面讲到的7.2节的就是这种

【继承】则是面向对象的一种特性,本篇文章就讲到了实现这种特性的两种方法,而不是特性本身


7.1组合语法

组合(composition)是一个将代码聚合的过程,其实现只需将对象引用置于新类中即可(我们称其为“创建一个成员对象”),如果组合是动态发生的,那么它通常被称为聚合(aggregation)
//新类的成员对象通常被声明为private,使得使用新类的客户端程序员不能访问它们

新类与用到的对象引用是【has-a】的关系,比如电脑 has-a CPU、内存、显卡...
  • 具体实现:在新类中new另外一个类的对象,以添加该对象的特性。
  • 注意:在类的域中产生现有类的对象,对象引用会被初始化为null,如果编译器简单地为每一个引用都创建默认对象,那么会在许多情况下增加不必要的负担。

如果想初始化这些引用,可以在代码中的下列位置进行:
  1. 在定义对象的地方。//这意味着它们总是能够在构造器被调用之前被初始化
  2. 在类的构造器中
  3. 惰性初始化。//在正要使用这些对象之前,某些情况下可以减少额外的负担
  4. 使用实例初始化


7.2继承语法

而继承则是一个将代码泛化的过程,在Java中一个子类只能有一个直接父类,想要实现多重继承,可以通过多级继承来实现(Java中接口解决了部分问题,而内部类有效地实现了“多重继承”)

其基类和导出类是【is-a】的关系,比如海鸥 is-a 鸟;乌鸦 is-a 鸟...
//如果要细分的话,则可以分为两种类型:
//【is-a】比较纯粹,称之为纯继承(导出类只具有与基类一样的接口)
//【is-like-a】则是在原类型上做扩展的结果(导出类拥有除基类外,属于自己的接口)

  • 具体实现:使用extends关键字
  • 作用:extends某个类时,会自动获得基类中所有的域和方法
  • 特点:在继承关系中,父类更通用、子类更具体。父类具有更一般的特征和行为,而子类除了具有父类的特征和行为,还具有一些自己特殊的特征和行为
  • 术语:表示父类和子类的术语:父类和子类、超类和子类、基类和派生类,他们表示的是同一个意思。
  • 当创建一个类时,除非显式的继承自某个类,否则就在隐式的继承Object类。
  • 继承的一般规则是将所有的数据成员都指定为private,将所有的方法指定为public。
  • 初始化基类
继承不只是复制基类的接口。当创建了一个导出类的对象时,该对象包含了一个基类的子对象,并且它的构建过程是从基类“向外”扩散的(实质上是基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次逐渐向上连接,从代码角度上说 导出类构造器的第一条代码总是显示或隐式的调用super()﹖,对象的创建顺序是从基类到导出类,这就是我们所说的”向外“扩散,更多关于初始化的知识在这里

re﹖:编译器为什么要强制每个导出类部分都必须调用父类构造器?
导出类只能访问它自己的成员,不能访问基类中的成员(基类成员通常声明成private类型【保证封装性】)。只有基类构造器才具有恰当的知识和权限来对自己的元素进行初始化。因此必须令所有构造器都得到调用,否则就不可能正确构造完整对象。

所以基类在导出类可以访问它之前就已经完成了初始化,即使你不为导出类创建构造器,编译器也会为你合成一个默认的构造器,该构造器将调用基类的构造器


7.3代理

如果我们一个类的方法去操纵另一个类的对象,那么这两个类之间就是一种“依赖”(uses-a)的关系,简单的说就是我们使用了代理。
  • Java不直接支持代理,但很多开发工具支持代理。例如IDEA就可以直接生成代理类
  • 可将代理看作组合与继承的拼接产物,换句话说这是继承与组合的之间的中庸之道
  • 怎么使用代理?代理的存在意义是什么?
设想我们现在有一个飞机控制类,其方法包括控制方向的up,down,left,right,forward,back方法,和发射导弹的launch方法

我们希望创建一个客机类(代理类),我们需要飞机控制类的控制方向的方法,但不希望拥有发射导弹的方法,如果我们使客机类继承于飞机控制类,那么它的所有方法将在客机类中暴露出来。代理解决了此难题:

  1. 我们要做的就是将一个成员对象(飞机控制类对象)置于要构造的类(客机类)中(就像组合)
  2. 将成员对象的方法封装在新类的方法里


  • 下面将举例说明,通过这个例子我们可以更直观的理解代理的概念,尽管我们都不太愿意去阅读代码,但事实证明,适当的例子可以加深我们对一个抽象概念的理解。


e.g.

class Plane{

    //1
    private PlaneControls controls = new PlaneControls();
    //2
    public void up(int velocity){

        cantrols.up(velocity);
        ...
    }

}
  • 代理和组合的定义很容易混淆,下面是代理及组合的定义,请仔细斟酌:
  1. 组合:在新类中new 另外一个类的对象,以添加该对象的特性。
  2. 代理:在代理类中创建某功能的类,调用类的一些方法以获得该类的部分特性。
这两者的区别:

组合是个部件之间没有关系,就像组装电脑,只需在电脑类里new CPU();new RAM();new....

代理则是成员对象是代理类的一部分,我们需要用到代理类的一些功能

  • 代理的优势:
我们使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中方法的子集


注意

  • 虽然编译器强制你去初始化基类,并且要求你要在构造器起始就要这么做,但是它并不监督你必须将成员对象也初始化,因此在这一点上你自己必须时刻注意
  • 保证正确清理:有时我们需要在类的生命周期做一些必须的善后工作(清理,),当类的继承结构复杂起来后,我们该如何保证正确清理呢?
首先,执行类的所有特定的清理动作,其顺序同生成顺序相反(通常这就要求基类元素依旧存活);然后,调用基类的清理方法。(将在之后更新的博文中详细说明)
  • 名称屏蔽:如果Java的某个基类拥有某个已被重载多次的方法名称,那么在导出类中重新定义该方法名称不会屏蔽其在基类中的任何版本(无论是在该层(类)或者它的基类中对方法进行定义,重载机制都可以正常工作)

技巧:利用Override注解,在你不留心重载而并非覆盖(覆写)该方法时,编译器会产生一条错误信息。它可以防止你在不想重载时而意外地进行了重载

继承技术其实是不太常用的,其使用场合仅限于你确信使用该技术确实有效的情况。一个最清晰的判断方法就是问一问自己是否需要从新类进行向上转型。如果需要,则继承是必要的。


总结

继承和组合都能从现有类型生成新类型。组合一般是将现有类型作为新类型底层实现的一部分来加以复用,而继承复用的是接口。
在开始一个设计时,一般优先选择使用组合(或者可能使用代理),只在确实必要时才使用继承

本文只供学习交流之用,尊重他人劳动成果,转载请注明出处。

Comments

Popular posts from this blog

抓包工具Wireshark下载及安装教程

HTTP协议特性

Java中Synchronized的用法