第64项:通过接口引用对象

  第51项建议:应该使用接口而不是类作为参数的类型。更一般的讲,应该优先使用接口而不是类来引用对象。如果有合适的接口类型存在,那么对于参数、返回值、变量和域来说,就都应该使用接口类型进行声明 。只有当你利用构造器创建某个对象的时候,才真正需要引用这个对象的类。为了更具体地说明这一点,我们来考虑 LinkedHashSet的情形,它是Set接口的一个实现。在声明变量的时候应该养成这样的习惯:

// Good - uses interface as type
Set<Son> sonSet = new LinkedHashSet<>();

  而不是像这样的声明:

// Bad - uses class as type!
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

  如果你养成了用接口作为类型的习惯,你的程序将会更加灵活 。当你决定更换实现时,所需要做的就只是改变构造器中类的名称(或者使用一个不同的静态工厂)。例如,为了阅读,可以把第一个声明改为:

Set<Son> sonSet = new HashSet<>();

  周围的所有代码都可以继续工作。周围的代码并不知道原来的实现类型,所以它们不会注意到这一改变。

  有一点值得注意:如果原来的实现提供了某种特殊的功能,而这种功能并不是这个接口的通用约定所要求的,并且代码依赖于该功能,那么新的实现也要提供同样的功能,这一点至关重要。例如,如果第一个声明周围的代码依赖于LinkedHashSet的排序策略,那么在声明中用HashSet替换LinkedHashSet是不正确的,因为HashSet不保证迭代顺序。

  那么,为什么要改变实现呢?因为新的实现提供了更好的性能,或者因为它提供了期望得到的额外的功能。例如,假设一个字段包含一个HashMap实例。 将其更改为EnumMap将提供更好的性能和键的自然顺序一致的迭代顺序,但如果键类型是枚举类型,则只能使用EnumMap。将HashMap更改为LinkedHashMap将提供可预测的迭代顺序,其性能可与HashMap相媲美,而不会对键的类型提出任何特殊要求。

  你可能会认为使用其【变量】的实现类型声明变量是可行的,因为你可以同时更改声明的类型和实现的类型,但是你无法保证这个更改可以使程序【顺利】编译。如果客户端代码使用了原始类型上的方法,这个方法在替换的类型当中不存在,或者客户端代码将实例传递给需要原始类型的方法时,在进行此修改后的代码就无法【成功】编译。使用接口类型声明变量可以保证你的诚实【使用接口类型声明变量让你成为了一个老实人!】。

  如果没有合适的接口存在,完全可以使用类而不是接口来引用对象 。例如,考虑值类(value class),例如String和BigInteger。记住,值类很少会用多个实现编写。它们通常是final的,并且很少有对应的接口。使用这种值类作为参数、变量、域或者返回类型是再合适不过了。

  不适合使用接口类型的第二种情形是,对象属于一个框架,而框架的基本类型是类,不是接口。如果对象属于这种基于类的框架(class-based framework),就应该用相关的基类(base class)(往往是抽象类)来引用这个对象,而不是用它的实现类。java.io里面的很多类就属于这种类型,比如OutputStream。

  不适合使用接口类型的最后一种情形是,类实现了接口,但是它提供了接口中不存在的额外方法————例如,PriorityQueue有一个comparator方法在当前的Wueue接口中是不存在的。仅当程序依赖于这些额外的方法的时候,像这样的类就可以使用类【类型】来引用对象,而且这种情况应该是很少的。

  这三种情况并不是详尽无遗的,而只是表达了一些“适合用于类来引用对象”的情形。在实践中,给定的对象是否具有适当的接口应该是很显然的。如果有,用接口引用对象就会使程序更加灵活;如果没有合适的接口,则使用类层次接口中提供了必要功能的最基础的类

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值