在设计模式原则中有“组合/聚合复用原则”,大部分网上说的概念比较笼统,不容易理解。因此将自己理解的内容记录下,供以后自己参考。
1. 组合与聚合的区别
- 聚合方式:A类的对象在创建时不会立即创建B类的对象,而是等待一个外界的对象传给它。
- 组合方式:A类的构造方法里创建B类的对象,也就是说,当A类的一个对象产生时,B类的对象随之产生。
组合和聚合意义不同,但是在java中的引用形式是相同的。
2. 为什么要继承代替组合
在网上中一直是如下回答的:
①继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
②子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
③它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
假设一个系统需要使用一个数据库,则会如下编写:
class DBUtil{
Connection getConnection(){
return "mysql connection";
}
}
如果使用了继承,其他的实体类的DBService如下:
class xxxDBService extends DBUtil {
void insertXX(){
super.getConnection()
xx;
}
}
(1)继承破坏了封装性
在DBUtil中getConnection方法是被封装了,但是xxDBservice继承了对应的类,这个时候xxDBService是可以重写对应的getConnection方法的,当然了一般而言的话也不会重写,但是如果重写了,并且没有保证里氏替换原则,这样的话就会有一定的风险。
(2)子类与父类的耦合度高
好的代码应该是需要高内聚,低耦合的,如果使用了继承的话,则其实是直接继承了细节,而不是抽象。在设计模式中有一个接口依赖原则,就是应该依赖接口,而不是依赖具体的实现类,子类和父类就绑定在一起了,这样如果父类修改了代码,则子类也会需要进行改动。
(3)限制了复用的灵活性
继承是继承了细节,这样编译后,是一个实实在在的代码了,无法在运行时灵活地改变getConnection中的内容了,如果系统需要在xxx环境下使用其他数据库,则继承是无法实现的,因为它不能在运行时根据改变其代码内容。可以使用组合加接口的方式,如下所示:
abstract class AbstractDButil{
Conection getConnection();
}
class MysqlDButil extends AbstractDButil {
Connection getConnection(){
return "mysql connection";
}
}
class OracleDButil extends AbstractDButil {
Connection getConnection(){
return "oracle connection";
}
}
class xxxDBService {
DButil dbutil;
xxxDBService (DButil dbutil){
this.dbutil = dbutil;
}
changeDButil(DButil dbutil){
this.dbutil = dbutil;
}
void insertXX(){
super.getConnection()
xx;
}
}
main(){
// 如果客户端选择了mysql作为存储
if(type="mysql"){
xxxDBService.changeDButil(new OracleDButil());
xxxDBService.insert();
}
}
二、总结
其实使用组合代替继承,能够加强类的扩展性,实现高内聚低耦合,这个原则也是体现了其他的原则;
如接口依赖原则:子类依赖了父类,而父类不是接口