调用 bazz 方法时,将调用哪个 bar 方法?当然是 B 类中的 bar 方法 。这会带来什么问题?问题在于 A 类中的 foo 方法不知道被 B 类中 bar 方法重写,由于 A 中的 foo 方法认为调用的是自己定义的 bar 方法,从而导致类中的不变量和封装遭到破坏 。这个问题也称为脆弱的基类问题(fragile base-class problem) 。
【如何在 Java 中安全地使用子类型】subtype 会引发更关键的耦合问题:程序中的一部分对另一部分产生非预期得依赖(紧耦合) 。强耦合的经典案例就是全局变量 。例如,如果修改全局变量的类型,那么使用该变量(即与变量耦合)的所有函数都可能受到影响,因此必须检查、修改和重新测试所有代码 。
此外,所有使用全局变量的函数都因为它彼此耦合 。也就是说,如果变量的值在某个不恰当的时间更改,那么一个函数可能错误地影响另一个函数的行为 。在多线程程序中,这种问题尤其可怕 。
2. 如何安全地 subclass
subclass 最安全的方法是避免 subtype 。如果类设计时并不希望支持 subclass,那么可以把构造函数设为 private 或在类的声明中加 final 关键字防止 subclass 。如果希望支持 subclasss,那么可以新建一个包装类(wrApper class)实现代码重用 。
这时,我们需要对代码重用进行模块化推理,即在无需了解实现细节的情况下重用代码的能力 。有几种方法可以做到这一点,这里介绍其中的一些方法 。一种方法是将可重写(overridable)的功能限制在少数 protected 方法中,避免自我调用可重写方法 。
例如,通过语言自身机制或规范来防止重写其他方法 。在 DataHashSet 类中,避免 addAll 调用 add 方法 。另外,避免在类中调用可重写方法减少重写对其他函数的直接影响 。让我们用前面的例子继续说明:
class A { void foo(){ ... this.insideBar(); ... } void insideBar(){ ... } void bar(){ this.insideBar(); }}class B extends A { // 重写 bar void bar(){ ... }}class C { void bazz(){ B b = new B(); B.foo(); }}在上面的代码中,仅仅添加了 insideBar 方法,防止重写导致不必要的更改,就可以解决问题 。大多数情况下,创建包装类是降低 subtype 风险的好方法 。相比 subtype 我更喜欢组合(composition)或委托(delegation) 。
有些时候必须不惜一切代价避免 subtype 。如果有不止一种方法实现 subtype,那么最好使用委托 。或者父类中包含一些没有调用的方法时,意味着不需要使用继承(Liskov 代换原则) 。同样的规则对 class 也适用 。我的意思是不应该在启用共享类(shared class)的时候对重用该类 。
译注:shared class 共享类技术 。Java5 平台的 IBM 实现中新的共享类特性提供了一种完全透明和动态的方法,可以共享已经装载的所有类,而不会对共享类数据的 JVM 施加限制 。这个特性为减少虚拟内存占用和改进启动时间提供了一个简单且灵活的解决方案,大多数应用程序都能够因此受益 。3. subtype 委托
subtype 模式可以把类看做模板,它定义了所有实例的结构 。每个实例都具备类属性与行为,但不包含属性值 。因为类的所有实例(包括子类的实例)都使用类定义的属性,所以对类属性的任何更改都将影响到所有实例 。
一个实例包含了父类(superclass)和子类所有信息的组合 。subtype 呈一种线性的上下关系(Java 与 C++ 不同,不能有多个子类) 。值存储在实例中,而不是类中,并且不支持共享 。在子类中,实例之间互相独立,更改一个实例的状态值不会影响任何其他实例,而且每个实例都有自己的父对象 。
委托表示使用另一个对象进行方法调用 。在委托实例中,不通过类共享属性或行为,通常称之为无类实例 。要重用某个类,可以使用它的一个实例 。假设有一个面积计算器类,能够接受不同形状并返回其计算的面积 。只要创建一个面积计算器对象,调用不同的面积计算类 。在子类中,针对每种类型的面积计算,必须创建一个带有父类型的独立对象 。
如果计算器对象将一个方法或变量委托给一个原型,那么修改任何属性或值都将同时影响对象和原型 。使用这种方式,委托关系中的对象会互相依赖 。在委托实现中,需要启动多个对象 。与 subtype 相反,对象可以是不同类型 。此外,还需要用正确的方式组合实例,以满足类的需要 。
由于没有父类,因此不能直接使用对象属性 。在 subtype 中,子类可以使用父类中的属性或方法;在委托中,必须先定义才能访问 。
在委托中,只需要建立同这些类的连接,一个重用类(reuse class)可以重复使用多个重用类,这些类都包含在同一个实例中 。但在 subtype 中,重用类必须是其他重用类的子类(具备继承关系) 。
推荐阅读
- Java虚拟机最多支持多少个线程的探讨
- PHP 多台服务器跨域如何让 session 共享使用
- 如何通过“import”语句确定在Python中导入了哪个文件?详解
- Java基于Solr海量数据搜索,搜索引擎的实现
- 纯JavaScript实现的调用设备摄像头并拍照的功能
- 如何优雅的处理API接口的返回数据-----附源码
- C++如何正确使用智能指针?看完这4个点你就明白了
- 什么是无需物流 物流需要解决的问题
- 阿里巴巴怎么开店注册流程 如何阿里巴巴开店步骤
- 苁蓉益肾颗粒(兰太)的说明书