Java有两种机制可以为某个抽象提供多种实现——Interfaceabstract class

Interface 和 abstract class,
除了比较明显的区别(也就是能否提供基本实现),
比较重要的区别是——
接口的实现类可以处于类层次的任何一个位置,而抽象类的子类则受到这一限制。

Existing classes can be easily retrofitted to implement a new interface.

即,如果一个类要实现某个接口,只需要加上implements语句和方法实现。
而继承一个抽象类则可能会破坏类层次,比如,属于两个不同类层次的类都想要某一个抽象类的行为时我们需要重新整理一下类层次。

Interfaces are ideal for defining mixins.

(“mixin”这一词不知该如何翻译,翻译为"混合类型"显得很僵硬。)
个人觉得这一条和第一条几乎是说明同一个问题。
关于mixin,作者用Comparable举例,其实现类表明自己的实例有相互比较的能力。
而抽象类不能随意更新到现有的类中,考虑到类层次结构,为类提供某个行为的抽象时接口更为合适。

Interfaces allow the construction of nonhierarchical type frameworks.

事实上类层次结构并不是一无是处的,但这需要我们思考:是否需要组织为严格的层次结构。
比如,歌手和作曲家是否需要层次结构? 显然他们没有层次关系。

即:

public interface Singer{
AudioClip sing(Song s);
} public interface Songwriter{
Song compose(boolean hit);
}

如果是创作型歌手,他需要同时扩展Singer和Songwriter。
幸好上面二者都是interface,于是我们可以:

public interface SingerSongwirter extends Singer, Songwriter{
AudioClip strum();
void actSensitive();
}

如果试图使用抽象类解决这一问题,
也许我们可以将一个类的对象作为另一个类的field,
也许我们也可以将它们组织成类层次关系。
但如果类的数量越来越多,出现更多的组合,结构变得越来越臃肿(ps:称为"combinatorial explosion")。

另外,说说接口比较明显的"缺点",也就是不能提供任何实现。
但需要注意的是,这个特征并不能使抽象类取代接口。
比较好的方法将两者结合起来,这种用法很常见。
比如Apache Shiro的DefaultSecurityManager的类层次(当然,Shiro还在不断完善中...):

即,为接口中的定义提供一个抽象的骨架实现(skeletal implementation),将接口和抽象类的优点结合起来。

通常,一个抽象类为接口提供skeletal实现时存在这样的命名规则,比如AbstractSet和Set、AbstractCollection和Collection。

如果我用这个skeletal实现,岂不是又要受类层次的困扰?
确实是这样,但skeletal的意义并不在于灵活性。
先举书中的代码例子,静态工厂方法使用skeletal类返回整型列表(ps:过度使用自动装拆箱...):

public class IntArrays {
static List<Integer> intArrayAsList(final int[] a) {
if (a == null)
throw new NullPointerException(); return new AbstractList<Integer>() {
public Integer get(int i) {
return a[i];
} @Override
public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val;
return oldVal;
} public int size() {
return a.length;
}
};
}
}

另外再举个Apache Shiro中的例子,在org.apache.shiro.realm.Realm接口中有这么一段说明:

Most users will not implement the Realm interface directly, but will extend one of the subclasses, {@link org.apache.shiro.realm.AuthenticatingRealm AuthenticatingRealm} or {@link org.apache.shiro.realm.AuthorizingRealm}, greatly reducing the effort requird to implement a Realm from scratch.

即,直接实现某个接口是个繁琐的工作。我们更建议使用其子类(当然,并不是必须),比如:

org.apache.shiro.realm.CachingRealm CachingRealm
org.apache.shiro.realm.AuthenticatingRealm AuthenticatingRealm
org.apache.shiro.realm.AuthorizingRealm

当然,也有简单实现类(simple implementation),比如:

org.apache.shiro.authc.pam.ModularRealmAuthenticator

下面是书中提供的编写skeletal的例子:

public abstract class AbstractMapEntry<K, V> implements Map.Entry<K, V> {
// Primitive operations
public abstract K getKey(); public abstract V getValue(); // Entries in modifiable maps must override this method
public V setValue(V value) {
throw new UnsupportedOperationException();
} // Implements the general contract of Map.Entry.equals
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?, ?> arg = (Map.Entry) o;
return equals(getKey(), arg.getKey())
&& equals(getValue(), arg.getValue());
} private static boolean equals(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
} // Implements the general contract of Map.Entry.hashCode
@Override
public int hashCode() {
return hashCode(getKey()) ^ hashCode(getValue());
} private static int hashCode(Object obj) {
return obj == null ? 0 : obj.hashCode();
}
}

相对于提供一个实现类,编写一个skeletal实现有一些限制。
首先必须了解接口中哪些是最基本的行为,并将其实现留给子类实现,skeletal则负责接口中的其他方法(或者一个都不实现)或者其特征相关的实现。
另外,skeletal类的价值在于继承,稍不注意就可能因为继承而破坏封装性。
为继承而设计的类还需要提供相应的文档,比如哪些方法是self-use、类实现了Serializable或者Clonnable等...

除了可以提供基本实现这一"优势",抽象类还有一个优势就是:抽象类的变化比接口的变化更容易。
即,在后续版本中为抽象类增加方法比接口增加方法更容易。
在不破坏实现类的情况下,在一个公有接口中增加方法,这是不可能的(即便下面是skeletal类,但一直detect下去总会有实现类存在)。
因此,设计接口是个技术活,一旦定下来就别想再变了。

抽象类和接口之间的选择,某种角度上可以说是易扩展性和灵活性之间的选择。

最新文章

  1. MongoDB学习-在.NET中的简单操作
  2. Scala入门之Array
  3. CCF第四题无向图打印路径
  4. jqgrid如何在一个页面点击按钮后,传递参数到新页面
  5. javaEE(web)SEO优化 Yahoo军规
  6. android PopupWindow实现从底部弹出或滑出选择菜单或窗口
  7. poj1474Video Surveillance(半平面交)
  8. UIkit框架之UIalert(iOS 9之后就不用这个了)
  9. HTML5,CSS3 与 Javascript 制作视频播放器
  10. 老李案例分享:MAT分析应用程序服务出现内存溢出过程
  11. 手写DotNet Core 认证授权代码
  12. hibernate 查询字段是重复名字的处理方法
  13. Hibernate多对一ManytoOne
  14. Java中集合删除元素时候关于ConcurrentModificationException的迷惑点
  15. java.lang.NoClassDefFoundError: org/apache/solr/common/params/SolrParams
  16. 【BZOJ3551】 [ONTAK2010]Peaks加强版
  17. # tail -f /var/log/zabbix/zabbix_agentd.log sudo: sorry, you must have a tty to run sudo
  18. beautiful number 数位DP codeforces 55D
  19. centos安装swoole
  20. 20155222 2016-2017-2 《Java程序设计》第4周学习总结

热门文章

  1. 用 go 写 WebAssembly入门
  2. HTTP调用接口方法
  3. 用TIdIPWatch获取本地IP
  4. Java容器中的元素输出
  5. 【ocp-12c】最新Oracle OCP-071考试题库(41题)
  6. “全栈2019”Java多线程第八章:放弃执行权yield()方法详解
  7. 0基础浅谈反射型xss(2)
  8. 看个AV也中招之cve-2010-2553漏洞分析
  9. URL的组成和含义
  10. Visual Studio进行负载测试,RIG和负载测试术语- Part II