有效的Java第3版,第18项:支持组合而不是继承描述了使用继承向类添加行为的问题:
子类中脆弱性的一个相关原因是它们的超类可以在后续版本中获取新方法。假设程序依赖于其安全性,因为插入到某个集合中的所有元素都满足某些谓词。这可以通过继承集合并覆盖每个能够添加元素的方法来保证,以确保在添加元素之前满足谓词。这可以正常工作,直到在后续版本中将能够插入元素的新方法添加到超类中。一旦发生这种情况,仅通过调用新方法就可以添加“非法”元素,而新方法不会在子类中被覆盖。
推荐的解决方案:
不是扩展现有的类,而是为新类提供一个引用现有类的实例的私有字段...新类中的每个实例方法都在现有类的包含实例上调用相应的方法并返回结果。这称为转发,新类中的方法称为转发方法 ...向现有类添加新方法对新类没有影响...编写转发方法很繁琐,但您必须为每个接口编写一次可重用转发类,并且可能会为您提供转发类。例如,Guava为所有集合接口提供转发类。
我的问题是,是否还存在将方法添加到转发类的风险,从而打破了子类的不变量?像Guava这样的外部库如何在不转移客户完整性风险的情况下在转发类中引入更新的方法?
答案 0 :(得分:4)
隐性假设似乎是你是编写转发类的人,因此你可以控制是否有任何东西被添加到它。无论如何,这是使用构图而不是继承的常用方法。
Guava示例似乎引用了the Forwarding Decorators,它们明确地设计为从中继承而来。但它们只是帮助创建这些转发类更简单,而无需在接口中定义每个方法;他们明确地不保护您免受将来可能需要覆盖的任何方法的侵害:
请记住,默认情况下,所有方法都直接转发给委托,因此覆盖
ForwardingMap.put
不会改变ForwardingMap.putAll
的行为。小心覆盖必须更改其行为的每个方法,并确保装饰的集合满足其合同。
所以,如果我理解这一切,番石榴就不是一个很好的例子。
答案 1 :(得分:2)
风险是否仍然存在方法也可以添加到转发类,从而打破子类的不变量?
组合是继承的替代方法,因此当您使用组合时,没有子类。如果向转发类添加新的公共方法(可以访问包含的实例的方法),则意味着您希望使用这些方法。
答案 2 :(得分:0)
因为您是转发类的所有者,所以只能为其添加新方法,从而保持不变量。