Probably going to disagree here... but that's ok !

I think IndexReader and IndexWriter would have been perfect interfaces - as long as the concepts were kept very abstract.

putDocument(), getDocument(), findDocument(), etc.

and supported the semantics.

That is what I find is key to hiding the implementation. If you start with very abstract concepts, the impl does not creep into the API.

When "things change" even with abstract classes, you still have a problem of sorts. Take locking for instance. If you never had locking in a system, and add it later, even though you could add methods like 'lockIndex()' and 'unlockIndex()' other clients that worked well before if they were not updated to call these methods properly would either fail, or lock the index from everyone else while they were open. Probably not good in either case. So adding concepts usually requires code changes anyway.

In the example you cite, there are two possibilities: 1) a MapContext can ALWAYS be created from a map,value,reporter. If this is the case, your API change is for programmer convenience only and can easily be performed in other ways. 2) this is not the case, then internally your code has two paths anyway, with branching logic based on the type of key

I think your statement 'static problem domains' is a bit incorrect. It depends on what you define as the problem. Going back to the TableModel interface, its problem domain could be stated as 'interface to tabular data for display and edit', and makes no assumption on how the data is stored, what the data is, etc.

Taken to Lucene, IndexWriter could be defined as 'place document in index suitable for later retrieval'.

Yes you need to know a bit more about the available operations, but if kept abstract enough, the interfaces follow quite easily, AND they don't paint you into a corner.

On Mar 19, 2008, at 1:01 PM, Doug Cutting wrote:

robert engels wrote:
The problem with abstract classes, is that any methods you provide "know" something of the implementation, unless the methods are implemented solely by calling other abstract methods (which is rarely the case if the abstract class contains ANY private members).

Yes, abstract classes should generally avoid private fields that don't have both setters and getters.

This is possible because the interfaces were designed very well. You MUST completely understand the problem domain in abstract terms in order to define proper interfaces.

That works for static problem domains. If Lucene is resolved to only make bugfix releases, and not to substantially evolve its feature set, then this might be appropriate.

IndexReader and IndexWriter should have been interfaces. If they were, lots of the code would not have been structured as it was, and many problems people had in producing "other" implementations could have been avoided.

The problem is not that they were not interfaces, but that they were not originally intended to be abstract and replaceable. The original design was that indexing would be the primary implementation that Lucene provided, and that things around indexing would be extensible, but that indexing itself would not be. Extensibility was retrofitted onto an existing design, and it still shows some.

If IndexReader and IndexWriter were originally written to be extensible it would have been foolish to implement them as interfaces given the amount that these have evolved. Each release would have broken every application.

As for future expansion, it is improbable in most cases that adding new abstract methods will work - if that is the case, they can easily be added to a static utility class. If the API is really changing/adding, it is easy to create 'interfaceV2 extends interfaceV1'. If the code worked before, and you want to support backwards code compatibility between versions, this is a fool proof way to accomplish it.

This is not foolproof. Not all extension is the addition of new methods. In Hadoop, for example, we wish to move from Mapper#map (key, value, reporter) to Mapper#map(MapContext), where MapContext has getKey(), getValue(), getReporter() and other methods. If Mapper were an abstract class, back-compatibility would be easy, since we could provide a default implementation of Mapper#map (MapContext) that calls Mapper#map(key, value, reporter). With interfaces things are much more complicated, since, for back- compatibility, we must support both versions of the interface for a time, dynamically determining what version of the interface the application has specified and calling it accordingly. This is ugly code that we could have avoided if we'd stuck to abstract classes. And the impact is not only where the Mapper is run, but also where it is specfied (JobConf). So instead of localizing the change to Mapper.java, we have to add lots of runtime support and public API methods in other classes. Yuck.

On the other hand, Hadoop's FileSystem is an abstract class. It has evolved considerably and applications have been able to upgrade without pain. Lucene's Directory has also evolved profitably without breaking external Directory implementations.

Interfaces look elegant, but looks can deceive.

Doug

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to