Effective Java读书笔记-复合优先于继承

继承是完成代码重用的有力手段,但它并非是完成这项工作的最佳工具。使用不当通常会导致软件非常的脆弱。在包的内部使用继承是非常安全的。对于普通的具体的类进行跨越包的边界的继承将是非常危险的。

继承打破了封装性:

与方法调用不同,继承打破了封装性。换句话说,子类依赖于超类中特定功能的实现细节。超类的实现有可能依赖发行版本的不同而有所变化,如果真的发生了变化,子类可能会遭到破坏,即使它的代码没有完全改变。因此子类必须跟着超类的更新而演变,除非超类是专门为了拓展设计的并且有很好的文档说明。

自用性:

假设你希望拓展HashSet的功能,并写了一个类InstrumentalHashSet类继承了HashSet类并重写了其中的addAll方法。假设你在客户端调用这样一条语句。

InstumentalHashSet<String>s = new InstrumentalHashSet<String>();
s.addAll(Arrays.asList("Snap","Crackle","Pop"));

当你调用getAddCount方法的时候你期待着返回3,但实际却返回了6。主要原因是当你在调用addAll方法的时候,add方法也被调用了。因此,最后的返回结果是6而不是3.这样一种方法在另一种方法实现的基础上进行实现叫做“自用性”。

导致子类脆弱的另一个原因:

假设超类在后续的发行版本中添加了新的方法,并且程序的安全性依赖这样的一个事实:所有被插入到某个集合中的元素都满足某个先决条件。下面的做法可以确保这一点:对集合中的元素进行子类化,并覆盖所有能添加元素的方法,则很有可能仅仅由于调用了这个未被子类覆盖的新方法,而将非法的元素添加到了子类当中。

避免子类脆弱的方法:

不需要拓展现有的类,而是在新的类中添加一个私有域,它引用现有类的一个实例,这种设计叫做“复合”。因为现有的类变成了新类中的一个组件。新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果。这被称作“转发”。新类中的方法被称作“转发方法”。这样得到的类将会非常的稳固,它不依赖于现有类的实现细节。即使现有的类添加了新的方法,也不会影响到新的类。

包装类与回调框架:

包装类不适合用在回调框架中,在回调框架中对象把自身的引用传递给了其它的对象,用于后续的调用,因为被包装起来的对象并不知道外面的包装对象,所以它传递一个指向自身的引用(this),回调避开了外面的包装对象。这被称作SELF问题。

使用继承的情况:

只有当子类真正是超类的子类型的时候,才适合使用继承。
对于两个类A和B,B是A的子类。在较为简单的API中,A本质上不是B的一部分,只是它的实现细节。

在Java平台类库中,有很多明显违反这条原则的地方。例如:栈(stack)并不是向量(vextor),所以stack不应该拓展vector。同样地,属性列表不是散列表,所以Properties不应该拓展HashTable。在这两种情况下复合才是最恰当的选择。

如果在该使用复合的地方使用了继承,则不可避免的会暴露实现的细节。

继承与复合的取舍:

在决定使用继承而不是复合之前应该问自己这样一个问题:对于你正在试图拓展的类,它的API有没有缺陷?如果有,你是否愿意把那些缺陷传递到类的API中?继承机制会把所有超类API中的缺陷传递到子类中去。而复合则允许新的API隐藏这些缺陷。

总之。继承的功能非常的强大,但是也存在很多问题,因为它违背了封装的原则。只有当子类和超类之间确实存在子类型关系的时候,使用继承才是恰当的选择。即便如此,如果子类和超类处在不同的包中,并且超类并不是为继承而设计的,那么继承就会导致脆弱性。为了避免这种脆弱性,可以使用复合和转发机制来代替继承,尤其是当存在适当的接口可以实现包装类的时候。包装类不仅比子类更加健壮,而且功能更加强大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值