附链
你也可以在这些平台阅读本文:
定义
尽量使用对象组合(contains-A)/聚合(has-A),而不是通过继承(is-A)达到软件复用的目的。
类的复用一般分为两种:一种是组合/聚合,另一种则是继承。
继承复用的优点在于扩展性较好,子类继承父类,父类的大部分功能都可以提供给子类使用,修改和扩展相对比较容易。
继承复用的缺点在于这种方式会破坏包装,继承会将父类的实现细节暴露给子类。
继承复用也叫白箱复用,组合聚合复用也叫黑箱复用。
场景示例
这里以数据访问层获取数据库连接,同时操作用户数据为例。
创建数据库连接类
创建一个数据库连接类,内部提供一个获取数据库连接的方法。
/**
* @author zhh
* @description 数据库连接
* @date 2020-02-09 13:45
*/
public class DBConnection {
/**
* 获取数据库连接
* @return
*/
public String getConnection() {
return "MySQL数据库连接";
}
}
创建用户数据访问层
创建一个用户数据访问类,基础数据库连接类的同时,提供一个新增用户的方法。
/**
* @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));
}
}
测试类及输出
/**
* @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数据库连接添加用户
类结构图
以上示例类的结构图如下所示
需求扩展
假设现在新旧系统合并,需要将旧有系统的 Oracle
数据库中的数据接入到新系统中。
而目前 DBConnection
中只会返回 MySQL
数据库的连接。
其实也简单,我们在 DBConnection
中再新增一个方法用于获取 Oracle
数据库的连接。功能实现上其实是没什么问题的,但是这样会违反开闭原则。
那如何利用合成复用原则对示例代码进行重构?代码如下:
创建数据库连接类
这里数据库连接类是一个抽象类,内部包含一个获取数据库连接的抽象方法,具体获取哪种数据库的连接则交于具体的子类去实现。
/**
* @author zhh
* @description 数据库连接
* @date 2020-02-09 13:45
*/
public abstract class DBConnection {
/**
* 获取数据库连接
* @return
*/
public abstract String getConnection();
}
创建具体数据库连接子类
/**
* @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数据库连接";
}
}
创建用户数据访问层
通过组合的方法将数据库连接注入到用户数据访问层当中。
/**
* @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()));
}
}
测试类及输出
/**
* @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
,而具体的选择则交由应用层。
类结构图
以上示例类的结构图如下所示
通过与场景示例中类图的对比,我们不难发现扩展后去掉了 UserDao
和 DBConnection
之间的继承关系,改成了一对一的组合关系。而 OracleConnection
和 MySQLConnection
都是继承 DBConnection
的,这一平级可以实现数据库连接的扩展,是符合开闭原则的。
优点
- 提高系统的灵活性
- 降低类与类之间的耦合
- 一个类的变化对其他类造成的影响相对较少
缺点
建造的系统会有较多的对象需要管理
参考
- 《Head First 设计模式》
- 《大话设计模式》
- 合成复用原则——面向对象设计原则