合成/聚合复用原则

  • 定义

        合成/聚合复用原则经常叫做合成复用原则。该原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。尽量使用对象组合,而不是继承来达到复用的目的

  • 概述

        在面向对象设计中,可以通过两种方法在不同的环境中复用已有的设计和实现,即通过组合/聚合关系或通过继承,但首先应该考虑使用组合/聚合,组合/聚合可以使用系统更加灵活,降低类与类之间的耦合度,一个类的变化对其他类的影响相对较少;其次才考虑继承,在使用继承时,需要严格遵循里氏替换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要谨慎使用继承复用。

  • 作用

        由于组合或聚合关系可以将已有的对象(也可称为成员对象)纳入到新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能,这样做可以使得成员对象的内部实现细节对于新对象不可见,所以这种复用又称为“黑箱”复用,相对继承关系而言,其耦合度相对较低,成员对象的变化对新对象的影响不大,可以在新对象中根据实际需要有选择性地调用成员对象的操作;合成复用可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。

  • 案例

        ○ 组合(Composition)contains-a关系

           组合关系表示事物的整体/部分关系的较强的情况,组合关系的类具有相同的生命周期。比如类A中包含了类B的一个引用b,当类A消亡时,b这个引用所指向的对象也同时消亡,这种情况称为组合,比如任何大脑。

        ○ 聚合(Aggregation)has-a关系

           聚合关系表示事物的整体/部分关系的较弱情况,当类A对象消亡时,类B不会消亡,因为可能还有其他对象指向了它。比如人和电脑。

        ○ 生动的案例

           一直野狼单独很难捕获到猎物,所以它们一直过着群居的生活,不容易被饿死,这样就有了狼群。每一只野狼都有自己的狼群,每个狼群都有好多野狼,野狼与狼群的这种关系就可以称之为聚合,另外每只野狼都有4条狼腿,野狼与狼腿的关系就叫做组合。

由此可见,聚合的关系明显没有组合紧密,野狼不会因为它们的群主将狼群解散而无法生存,而狼腿就无法脱离野狼而单独生存。

  • 组合关系

java复合类型 java合成复用原则_复用

//野狼类
public class Wolf {
    //狼腿
    public Legs legs;
    
    public Wolf(){
        legs = new Legs();    
    }
}

组合关系的类里含有另一个类的实例化。

野狼类(Wolf)在实例化之前,一定要先实例化狼腿类(Legs)两个类紧密耦合在一起,它们有相同的生命周期,狼腿类(Legs)不可以脱离野狼类(Wolf)而独立存在。

在组合关系中,客户端只认识野狼类,根本就不知道狼腿类的存在,因为狼腿类被严密的封装在野狼类中。

  • 聚合关系

java复合类型 java合成复用原则_java复合类型_02

//狼群类
class WolfGroup {
    //狼
    public Wolf wolf;
    
    public WolfGroup(Wolf wolf){
        this.wolf = wolf;    
    }
}

        聚合关系的类里含有另一个类作为参数。

        狼群类(WolfGroup)的构造函数中要用到野狼(Wolf)作为参数把值传进去,野狼类(Wolf)可以脱离狼群而独立存在

        在聚合关系中,客户端可以同时了解狼群类和野狼类,因为他们都是独立的

        ○ 经典案例

        在我们的日常开发中,经常遇到数据源切换的问题,比如早期我们采用的是MySQL数据库,为此我们开发了一个针对MySQL数据库的MySQLBDUtil类,主要给其他Dao层提供SQL操作,该DBUtil中封装了数据库的连接,执行及关闭方法。由于该类给每个Dao都提供支持,考虑到重用性,所有的Dao层都继承MySQLDBUtil类,这样方便直接调用数据库执行方法,就这样用得很愉快。

java复合类型 java合成复用原则_复用_03

但随着客户需求的变更,现要切换成Oracle数据库,所以我们新增了一个OracleDBUtil类来支持该数据库的连接、执行及关闭功能。由于之前的Dao层设计为继承MySQLDBUtil,所以无法继续继承OracleDBUtil,这样也违反开闭原则。现在采用组合复用原则对其代码进行重构。

根据合成复用原则,我们在实现复用时应该多用关联,少用继承。因此在本实例中我们可以使用关联复用来取代继续复用,重构后的结构如下图所示

java复合类型 java合成复用原则_设计模式_04

在上图中UserDao和DBUtil中的关系由继承关系变为关联关系,采用依赖注入的方式将DBUtil注入到UserDao中;如果需要对DBUtil的功能进行扩展,可以通过其子类来实现,如通过子类OracleDBUtil来连接Oracle数据库,通过MySQLDBUtil来连接MySQL数据库,根据里氏替换原则DBUtil的子类可以覆盖DBUtil对象,只需要将传入对象换成子类即可,原有代码无需修改,而且可以更加灵活的增加新的数据库连接方式。

  • 总结

        合成和聚合均是关联关系的特殊情况,它的优点是能够直接黑箱复用组合类的其他属性和行为,因为组合对象内部细节是当前对象无法看见的,所以这种复用所需的依赖较少,组合类内部的实现动态发生改变对当前对象造成的影响会比较少。