最近在翻阅Hadoop早期的jira list,对于一些重要的jira,会有很多贡献者的详细讨论,包括Doug Cutting, Tom White, Arun Murthy, Owen O'Malley等一批Hadoop领军人物的思想、观点,具有极强的学习价值。
今天在阅读Hadoop-3412和HADOOP-1230,当中Doug Cutting阐述了如何选择Interface和Abstract Class的观点。虽然这是个老生常谈的问题,但是Cutting作为极有经验的工程师,多个开源项目的创始者,从平台API evolution这个角度来讨论,很有参考性。
Cutting在HADOOP-1230中的原话如下:
The strategy here is to remove stuff that is likely to evolve from interfaces that users implement, so that we canchange it without breaking user implementations.
Note that, in general, interfaces are much more difficult to evolve than abstract base classes, and should thus only be used when "mixin" behavior is required, e.g., when one needs to be able to add an interface to an existing class. For example, interfaces like Serializeable and Writable are great, since folks might need to have a class that implements both, which wouldn't be possible if they were abstract base classes.
Some folks seem to believe that interfaces are somehow 'cleaner' and should be used for all public facing APIs. But that's not true: interfaces are limited to a subset of what abstract base classes can do, andan abstract class's ability to provide default implementations of methods greatly facilitates API evolution. Again (at the risk of sounding repetitive), the only advantage of interfaces is that a class can implement more than one of them.
So, as we revisit our core APIs that users implement for the purpose of making them more easily backward compatible,we should use abstract base classes in place of interfaces whenever it is clear that multi-inheritance is not a requirement.
可以看到,Cutting的观点很明确,除非你要有mixing behaviour,也就是说要多继承,否则总是选择abstract class。本质的原因是abstract class可以提供default implementation,当你想要对某个API做修改时,它提供了更大的灵活性,使得你不需要改API interface,从而保持向后兼容性。
举个例子,假如你有一个API class如下:
abstract class JobInProgressListener {
public void jobAdded(JobInProgress job) throws IOException{
//do nothing
}
}
如果未来你想要做修改,比如增加一个参数,你可以不用动原来的interface,而是增加一个overloaded方法,并且让原来的方法用默认参数调用新方法:
abstract class JobInProgressListener {
public void jobAdded(JobInProgress job) throws IOException{
jobAdded(job, new Configuration());
}
public void jobAdded(JobInProgress job, Configuration conf) throws IOException{
//do something
}
}
如果JobInProgressListener是interface,你只能改变原来contract,从而break原来的客户代码。