👉文章示例代码👈

定义

尽量使用对象组合(contains-A)/聚合(has-A),而不是通过继承(is-A)达到软件复用的目的。

类的复用一般分为两种:一种是组合/聚合,另一种则是继承

继承复用的优点在于扩展性较好,子类继承父类,父类的大部分功能都可以提供给子类使用,修改和扩展相对比较容易。

继承复用的缺点在于这种方式会破坏包装,继承会将父类的实现细节暴露给子类。

继承复用也叫白箱复用组合聚合复用也叫黑箱复用

场景示例

这里以数据访问层获取数据库连接,同时操作用户数据为例。

创建数据库连接类

创建一个数据库连接类,内部提供一个获取数据库连接的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author zhh
* @description 数据库连接
* @date 2020-02-09 13:45
*/
public class DBConnection {

/**
* 获取数据库连接
* @return
*/
public String getConnection() {
return "MySQL数据库连接";
}
}

创建用户数据访问层

创建一个用户数据访问类,基础数据库连接类的同时,提供一个新增用户的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author zhh
* @description 用户数据访问层
* @date 2020-02-09 13:51
*/
public class UserDao extends DBConnection {

/**
* 新增用户
*/
public void addUser() {
String connection = super.getConnection();
System.out.println(String.format("使用%s添加用户", connection));
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author zhh
* @description 测试类
* @date 2020-02-09 13:53
*/
public class Test {

public static void main(String[] args) {
UserDao userDao = new UserDao();
userDao.addUser();
}
}

测试类的输出结果如下:

使用MySQL数据库连接添加用户

类结构图

以上示例类的结构图如下所示

image.png

需求扩展

假设现在新旧系统合并,需要将旧有系统的 Oracle 数据库中的数据接入到新系统中。

而目前 DBConnection 中只会返回 MySQL 数据库的连接。

其实也简单,我们在 DBConnection 中再新增一个方法用于获取 Oracle 数据库的连接。功能实现上其实是没什么问题的,但是这样会违反开闭原则。

那如何利用合成复用原则对示例代码进行重构?代码如下:

创建数据库连接类

这里数据库连接类是一个抽象类,内部包含一个获取数据库连接的抽象方法,具体获取哪种数据库的连接则交于具体的子类去实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author zhh
* @description 数据库连接
* @date 2020-02-09 13:45
*/
public abstract class DBConnection {

/**
* 获取数据库连接
* @return
*/
public abstract String getConnection();
}

创建具体数据库连接子类

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
/**
* @author zhh
* @description MySQL数据库连接
* @date 2020-02-09 14:16
*/
public class MySQLConnection extends DBConnection {

@Override
public String getConnection() {
return "MySQL数据库连接";
}
}

/**
* @author zhh
* @description Oracle数据库连接
* @date 2020-02-09 14:16
*/
public class OracleConnection extends DBConnection {

@Override
public String getConnection() {
return "Oracle数据库连接";
}
}

创建用户数据访问层

通过组合的方法将数据库连接注入到用户数据访问层当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @author zhh
* @description 用户数据访问层
* @date 2020-02-09 13:51
*/
public class UserDao {

private DBConnection connection;

public void setConnection(DBConnection connection) {
this.connection = connection;
}

/**
* 新增用户
*/
public void addUser() {
System.out.println(String.format("使用%s添加用户", connection.getConnection()));
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author zhh
* @description 测试类
* @date 2020-02-09 13:53
*/
public class Test {

public static void main(String[] args) {
UserDao userDao = new UserDao();
userDao.setConnection(new MySQLConnection());
userDao.addUser();

userDao.setConnection(new OracleConnection());
userDao.addUser();
}
}

测试类的输出结果如下:

使用MySQL数据库连接添加用户
使用Oracle数据库连接添加用户

如果还有扩展,则只要写与 MySQLConnection 平级的类继承 DBConnection ,而具体的选择则交由应用层。

类结构图

以上示例类的结构图如下所示
image.png

通过与场景示例中类图的对比,我们不难发现扩展后去掉了 UserDaoDBConnection 之间的继承关系,改成了一对一的组合关系。而 OracleConnectionMySQLConnection 都是继承 DBConnection 的,这一平级可以实现数据库连接的扩展,是符合开闭原则的。

优点

  • 提高系统的灵活性
  • 降低类与类之间的耦合
  • 一个类的变化对其他类造成的影响相对较少

缺点

建造的系统会有较多的对象需要管理

参考