又一次认识java(四) — 组合、聚合与继承的爱恨情仇

有人学了继承,认为他是面向对象特点之中的一个,就在全部能用到继承的地方使用继承,而不考虑到底该不该使用,无疑。这是错误的。那么。到底该怎样使用继承呢?

java中类与类之间的关系

大部分的刚開始学习的人仅仅知道java中两个类之间能够是继承与被继承的关系。可是其实。类之间的关系大体上存在五种—继承(实现)、依赖、关联、聚合、组合。

接下来,简单的分析一下这些关系。

继承(实现)

对于类来说,这样的关系叫做继承,对于接口来说,这样的关系叫做实现。继承上一篇文章已经详细的解说过了,至于实现,我想大家也都知道是怎么回事,由于后面要专门讲接口,所以这里就先不说了。继承是一种“is-a”关系。

依赖

依赖简单的理解,就是一个类A中的方法使用到了还有一个类B。

这样的使用关系是具有偶然性的、暂时性的、非常弱的。可是B类的变化会影响到A。

比方说,我用笔写字,首先须要一个类来代表我自己,然后须要一个类来代表一支笔,最后。‘我’要调用‘笔’里的方法来写字,用代码实现一下:

public class Pen {
    public void write(){
        System.out.println("use pen to write");
    }
}

public class Me {
    public void write(Pen pen){//这里,pen作为Me类方法的參数
        pen.write();
    }
}

看到这大家都懂了,由于这样的代码你每天都会写。

如今你知道了,这就是一种类与类之间的关系,叫做依赖。

这样的关系是一种非常弱的关系,可是pen类的改变,有可能会影响到Me类的结果,比方我把pen类write方法的方法体改了,me中再调用就会得到不同的结果。

一般而言,依赖关系在Java中体现为局域变量、方法的形參。或者对静态方法的调用。

关联

关联体现的是两个类、或者类与接口之间语义级别的一种强依赖关系。

这样的关系比依赖更强、不存在依赖关系的偶然性、关系也不是暂时性的,通常是长期性的。并且两方的关系通常是平等的、关联能够是单向、双向的。

看以下这段代码:

// pen 还是上面的pen
public class You {
    private Pen pen; // 让pen成为you的类属性 

    public You(Pen p){
        this.pen = p;
    }

    public void write(){
        pen.write();
    }
}

被关联类B以类属性的形式出如今关联类A中,或者关联类A引用了一个类型为被关联类B的全局变量的这样的关系,就叫关联关系。

在Java中,关联关系一般使用成员变量来实现。

聚合

聚合是关联关系的一种特例。他体现的是总体与部分、拥有的关系。即has-a的关系

看以下一段代码:

public class Family {
    private List<Child> children; //一个家庭里有很多孩子

    // ...
}

在代码层面。聚合和关联关系是一致的。仅仅能从语义级别来区分。

普通的关联关系中,a类和b类没有必定的联系。而聚合中,须要b类是a类的一部分,是一种”has-a“的关系,即 a has-a b; 比方家庭有孩子,屋子里有空调。

可是。has 不是 must has。a能够有b,也能够没有。a是总体,b是部分,总体与部分之间是可分离的,他们能够具有各自的生命周期。部分能够属于多个总体对象,也能够为多个总体对象共享。

不同于关联关系的平等地位。聚合关系中两个类的地位是不平等。

组合

组合也是关联关系的一种特例。他体现的是一种contains-a的关系。这样的关系比聚合更强。也称为强聚合。

先看一段代码:

public class Nose {
    private Eye eye = new Eye();  //一个人有鼻子有眼睛
    private Nose nose = new Nose();

    // .... 
}

组合相同体现总体与部分间的关系。但此时总体与部分是不可分的,总体的生命周期结束也就意味着部分的生命周期结束。

就像你有鼻子有眼睛。假设你一不小心结束了生命周期,鼻子和眼睛的生命周期也会结束,并且,鼻子和眼睛不能脱离你单独存在。

仅仅看代码。你是无法区分关联,聚合和组合的,详细是哪一种关系,仅仅能从语义级别来区分。

相同,组合关系中,两个类额关系也是不平等的。

组合,聚合和继承

依赖关系是每个java程序都离不开的。所以就不单独讨论了,普通的关联关系也没有什么特殊的地方,以下我们重点研究一下组合,聚合和继承。

聚合与组合

  1. 聚合与组合都是一种关联关系,仅仅是额外具有总体-部分的意义。

  2. 部件的生命周期不同

    • 聚合关系中。整件不会拥有部件的生命周期,所以整件删除时,部件不会被删除。再者,多个整件能够共享同一个部件。

    • 组合关系中,整件拥有部件的生命周期,所以整件删除时。部件一定会跟着删除。

      并且,多个整件不能够同一时候间共享同一个部件。

    这个差别能够用来区分某个关联关系到底是组合还是聚合。两个类生命周期不同步。则是聚合关系,生命周期同步就是组合关系。

  3. 聚合关系是【has-a】关系,组合关系是【contains-a】关系。

    平时我们仅仅讨论组合和继承的时候,认为组合是【has-a 】关系,而其实,聚合才是真正的【has-a】关系。组合是更深层次的【contains-a】关系。

    由于【contains-a】关系是一种更深的【has-a】关系,所以说组合是【has-a】关系也是正确的。

组合和继承

这个才是本文的重点。

学过设计模式的都知道。要“少用继承。多用组合”,这到底是为什么呢?

我们先来看一下组合和继承各自的优缺点:

组合和继承的优缺点

组合

长处:

- 不破坏封装。总体类与局部类之间松耦合,彼此相对独立
- 具有较好的可扩展性
- 支持动态组合。

在执行时。总体对象能够选择不同类型的局部对象 - 总体类能够对局部类进行包装,封装局部类的接口,提供新的接口

缺点:

- 总体类不能自己主动获得和局部类相同的接口
- 创建总体类的对象时,须要创建全部局部类的对象

缺点分析:

1、总体类不能自己主动获得和局部类相同的接口

假设父类的方法子类中差点儿都要暴露出去,这时可能会认为使用组合非常不方便,使用继承似乎更简单方便。但从还有一个角度讲,实际上或许子类中并不须要暴露这些方法,client组合应用就能够了。

所以上边推荐不要继承那些不是为了继承而设计的类,一般为了继承而设计的类都是抽象类。

2、创建总体类的对象时。须要创建全部局部类的对象

这个可能没什么更好的办法,但在实际应用中并没有多出多少代码。

继承

长处:

- 子类能自己主动继承父类的接口
- 创建子类的对象时,无须创建父类的对象

缺点:

- 破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
- 支持扩展,可是往往以添加系统结构的复杂度为代价
- 不支持动态继承。

在执行时。子类无法选择不同的父类 - 子类不能改变父类的接口

缺点分析:

1、为什么继承破坏封装性?

这里写图片描写叙述

鸭子中不想要“飞”的方法。但由于继承无法封装这个没用的“飞”方法 。

2、为什么继承紧耦合:

这里写图片描写叙述

当作为父类的BaseTable中感觉Insert这个名字不合适时。假设希望将其改动成Create方法,那使用了子类对象Insert方法将会编译出错,可能你会认为这改起来还算easy。由于有重构工具一下子就好了并且编译错误改起来非常easy。

但假设BaseTable和子类在不同的程序集中,维护的人员不同。BaseTable程序集升级。那本来能用的代码忽然不能用了,这还是非常难让人接受的

3、为什么继承扩展起来比較复杂

这里写图片描写叙述

当图书和数码的算税方式和数码产品一样时,而消费类产品的算税方式是还有一样时,假设採用继承方案可能会演变成例如以下方式:

这里写图片描写叙述

这样假设产品继续添加,算税方式继续添加,那继承的层次会非常复杂。并且非常难控制。而使用组合就能非常好的解决问题

4、继承不能支持动态继承

这个其实非常好理解,由于继承是编译期就决定下来的,无法在执行时改变。如3例中。假设用户须要依据当地的情况选择计税方式。使用继承就解决不了。而使用组合结合反射就能非常好的解决。

5、为什么继承,子类不能改变父类接口

如2中的图,子类中认为Insert方法不合适,希望使用Create方法,由于继承的原因无法改变

组合与继承的差别和联系

  • 在继承结构中,父类的内部细节对于子类是可见的。

    所以我们通常也能够说通过继承的代码复用是一种 白盒式代码复用。

    (假设基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性)

  • 组合是通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。由于在对象之间。各自的内部细节是不可见的。所以我们也说这样的方式的代码复用是黑盒式代码复用 。(由于组合中一般都定义一个类型,所以在编译期根本不知道详细会调用哪个实现类的方法)

  • 继承在写代码的时候就要指名详细继承哪个类,所以。在编译期就确定了关系。(从基类继承来的实现是无法在执行期动态改变的。因此减少了应用的灵活性。)

  • 组合,在写代码的时候能够採用面向接口编程。所以,类的组合关系一般在执行期确定。

  • 组合(has-a)关系能够显式地获得被包括类(继承中称为父类)的对象,而继承(is-a)则是隐式地获得父类的对象,被包括类和父类相应,而组合外部类和子类相应。

  • 组合是在组合类和被包括类之间的一种松耦合关系。而继承则是父类和子类之间的一种紧耦合关系。

  • 当选择使用组合关系时,在组合类中包括了外部类的对象。组合类能够调用外部类必须的方法,而使用继承关系时,父类的全部方法和变量都被子类无条件继承,子类不能选择。

  • 最重要的一点,使用继承关系时,能够实现类型的回溯,即用父类变量引用子类对象,这样便能够实现多态,而组合没有这个特性。

  • 还有一点须要注意,假设你确定复用另外一个类的方法永远不须要改变时,应该使用组合。由于组合仅仅是简单地复用被包括类的接口,而继承除了复用父类的接口外。它甚至还能够覆盖这些接口,改动父类接口的默认实现,这个特性是组合所不具有的。

  • 从逻辑上看,组合最主要地体现的是一种总体和部分的思想。比如在电脑类是由内存类,CPU类。硬盘类等等组成的,而继承则体现的是一种能够回溯的父子关系。子类也是父类的一个对象。

  • 这两者的差别主要体如今类的抽象阶段。在分析类之间的关系时就应该确定是採用组合还是採用继承。

  • 引用网友的一句非常经典的话应该更能让大家分清继承和组合的差别:组合能够被说成“我请了个老头在我家里干活” 。继承则是“我父亲在家里帮我干活”。

继承还是组合?

首先它们都是实现系统功能重用,代码复用的最经常使用的有效的设计技巧,都是在设计模式中的基础结构。

非常多人都知道面向对象中有一个比較重要的原则『多用组合、少用继承』或者说『组合优于继承』。

从前面的介绍已经优缺点对照中也能够看出,组合确实比继承更加灵活。也更有助于代码维护。

所以。建议在相同可行的情况下。优先使用组合而不是继承。

由于组合更安全,更简单,更灵活。更高效。

注意。并非说继承就一点用都没有了,前面说的是【在相同可行的情况下】。

有一些场景还是须要使用继承的。或者是更适合使用继承。

继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个推断方法是。问一问自己是否须要从新类向基类进行向上转型。假设是必须的,则继承是必要的。反之则应该好好考虑是否须要继承。

仅仅有当子类真正是超类的子类型时。才适合用继承。换句话说,对于两个类A和B,仅仅有当两者之间确实存在 is-a 关系的时候。类B才应该继承类A。

向上转型将会在下一篇《又一次认识Java(五) — 面向对象之多态》中详细解说。

总结

依据我们前面讲的内容我们能够发现继承的缺点远远多于长处,虽然继承在学习OOP的过程中得到了大量的强调。但并不意味着应该尽可能地到处使用它。相反。使用它时要特别谨慎。

仅仅有在清楚知道继承在全部方法中最有效的前提下。才可考虑它。 继承最大的长处就是扩展简单,但大多数缺点都非常致命,可是由于这个扩展简单的长处太明显了。非常多人并不深入思考,所以造成了太多问题。

最后,总结一下:

1、精心设计专门用于被继承的类。继承树的抽象层应该比較稳定,一般不要多于三层。
2、对于不是专门用于被继承的类,禁止其被继承。
3、优先考虑用组合关系来提高代码的可重用性。
4、子类是一种特殊的类型,而不仅仅是父类的一个角色
5、子类扩展。而不是覆盖或者使父类的功能失效

没错,写了这么多,就是想说: 请谨慎使用继承。除非你确定非用继承不可!


这篇文章写得比較粗糙,由于写文章的时候一直在拉肚子。。。以后还会做一些改动。暂时先这样。假设文中有错误或者有更好的解释。欢迎给我留言。我也仅仅是一个学习的人。而不是一个Java大神,所以不保证文章内容的正确性~

參考文章:
http://www.cnblogs.com/nuaalfm/archive/2010/04/23/1718453.html
http://xifangyuhui.iteye.com/blog/819498
http://www.tuicool.com/articles/u2uUZjb
http://www.cnblogs.com/jiqing9006/p/5915023.html

本文地址:http://blog.csdn.net/qq_31655965/article/details/54645220,转子请注明出处。


看完点个赞呗。

原文地址:https://www.cnblogs.com/jzssuanfa/p/7360306.html