为何继承比组合耦合度更高?求private/public成员场景下的示例
一、Private成员场景:间接行为依赖导致的耦合
你说基类private成员不会暴露给子类,这点没错,但继承关系下,子类依然会耦合于基类private成员支撑的行为逻辑——基类private字段的改动会间接影响其public/protected方法的表现,而子类继承了这些方法,就会被迫跟着受影响。组合则可以规避这种被动依赖。
举个具体例子:
假设我们有一个基类FileLogger,内部有private的logQueue队列,提供public方法log(String msg)负责把消息加入队列,异步写入文件。
public class FileLogger { private Queue<String> logQueue = new LinkedList<>(); public void log(String msg) { logQueue.add(msg); // 异步写入逻辑 } }
子类JsonFileLogger继承FileLogger,通过调用父类的log方法来记录JSON格式的日志:
public class JsonFileLogger extends FileLogger { public void logJson(Object data) { String json = toJson(data); // 把对象转成JSON字符串 super.log(json); } }
现在如果FileLogger的实现改了:把private的logQueue换成batchLogList,并且给log方法加了限制——消息长度超过100就直接丢弃(之前没有这个规则):
public class FileLogger { private List<String> batchLogList = new ArrayList<>(); public void log(String msg) { if (msg.length() > 100) { return; // 超过长度直接丢弃 } batchLogList.add(msg); // 批量写入逻辑 } }
这下JsonFileLogger的logJson方法就出问题了:如果生成的JSON字符串超过100长度,日志会悄悄丢失,子类必须修改代码(比如先截断JSON、或者提前校验长度)才能适配这个变化——这就是继承带来的强耦合:基类内部实现的改动,间接强制子类修改。
如果用组合实现JsonFileLogger,情况就完全不同:
public class JsonFileLogger { private FileLogger logger; public JsonFileLogger(FileLogger logger) { this.logger = logger; } public void logJson(Object data) { String json = toJson(data); if (json.length() > 100) { json = json.substring(0, 97) + "..."; // 主动截断适配 } logger.log(json); } }
当FileLogger的log方法逻辑变化时,JsonFileLogger可以主动选择适配方式,甚至可以直接替换成其他Logger实现(比如MemoryLogger),完全不需要被动跟着基类改——耦合度低得多。
二、Public成员场景:API膨胀与强制继承的耦合
你觉得public成员在继承和组合下没区别,但实际上继承会让子类被动继承所有public方法,哪怕子类不需要这些方法,这会导致API膨胀,还可能引发逻辑冲突;而组合可以严格控制暴露的方法,避免不必要的耦合。
比如我们想实现一个只读列表:
如果用继承,直接继承ArrayList:
public class ReadOnlyList extends ArrayList<String> { @Override public boolean add(String s) { throw new UnsupportedOperationException("只读列表不支持添加"); } @Override public String remove(int index) { throw new UnsupportedOperationException("只读列表不支持删除"); } }
问题来了:ArrayList的public方法有几十个(比如addAll、clear、set等),我们得把所有修改类的方法都重写一遍抛出异常,而且如果后续ArrayList新增了public的修改方法(比如addFirst),ReadOnlyList会自动继承这个方法,用户调用时就会触发运行时错误——子类被迫绑定基类的所有public API,耦合度极高。
如果用组合实现:
public class ReadOnlyList { private List<String> innerList; public ReadOnlyList(List<String> innerList) { this.innerList = new ArrayList<>(innerList); // 拷贝一份避免外部修改 } public String get(int index) { return innerList.get(index); } public int size() { return innerList.size(); } // 只暴露只读相关的方法 }
这里ReadOnlyList只提供自己需要的get、size方法,ArrayList的其他public方法完全不会出现在它的API里。哪怕ArrayList后续新增任何方法,都不会影响ReadOnlyList的接口和逻辑——我们只依赖自己需要的部分,耦合度大幅降低。
内容的提问来源于stack exchange,提问作者cobby

