I've been chafing at the limitations of OO programming, particularly v-table style. And interfaces is one of the problem areas, because I want to re-use implementations.
I'm reading "The Paradoxical Success of Aspect-Oriented Programming" by Friedrich Steimann, and he talks about modularity. Parnas1 defined modularity as restricting the number of programmers that have to know about some implementation2. That's right, not a textual, or language issue. A person issue. This leads Stiemann to talk about interfaces, and leads me to distinguishing interface from implementation.
First, "class" should be the public-interface (the API), and inheritance should be on the basis of interfaces only. Because we want Liskov substitution3. The interface is how you can use an object, or, if you prefer, how it behaves. Typically, the interface is a list of method signatures. There is no implementation. Interestingly, some of the functional languages define class this way (e.g. Clean, and I think Haskell). Unfortunately, while some languages like Java can support this, it is a bit tedious, though much of Java's standard libraries are done this way. Even PHP has interfaces for things like the standard Array behavior.
This use of "interface" is different than the historic understanding related to modules, according to Stiemann4 ("class" being a restricted form of module). We should remember that this use is a a shorthand for "class interface," or some such.
Second, an "implementation" is a data-structure plus procedures that provide the interface5. All this, I would say, in a module, since we want to localize the implementation and let other programmers be oblivious. You don't inherit from implementations, though you should be able to re-use them.
The StdC++Lib is built this way. If you haven't studied this library (particularly the original STL documents), you should. Interesting that a language with poor support for the separation of interface/implementation should have the premier example. Note that part of the interface, e.g. complexity-guarantees, are not expressible in C++.
Any algorithm that is polymorphic could be provided as a default implementation in the interface. For example, "map", "filter", and "foreach" can be written in terms of "fold" without caring about the actual data-structure. An implementation can specialize any of these algorithms for optimization, or whatever. Clean (and Haskell?) do this.
Why this distinction between implementation and interface? Because, I can now do what we all want to do: re-use implementations. I believe that's what 90% of OO programmers do when they use inheritance. And, they get frustrated when they want to re-use implementation, but it's not compatible with class-inheritance. And, it is now clear that they are 2 different things. This separation is more natural in a multi-method language, since methods don't have to belong to the class's module, and polymorphic methods are more common (and easier).
When I re-use implementation by common class-inheritance, I want the vast majority of the implementation to stay the same, and change a few bits of it. For example, you make Model objects by inheriting from ActiveRecord. And in one case, I needed to customize the PK behaviors.
Or, more radically, haven't you been tempted to use multiple inheritance to get the implementation of something (say a hash/dict) but to use it as extending a some class? I know, "delegation" or some-such is the purported solution here, but it's tedious and annoying.
Third, there is one more level of distinction. When an algorithm for a method is complex enough, you'll naturally factor it into helper functions. Someone re-using your implementation should be able to override relevant helper-functions. And that is dangerous, because there is no agreement that the implementation is made up of so-and-so helper-functions, interacting in such-and-such an order. Our assumption is that the implementation is changeable (e.g. RoR's ActiveRecord changed between versions).
We need an analog of "interface" for algorithms. Which means we need something like the idea of "module" for the method and its helpers, with a declaration about behavior and what can be overridden6. Steimann calls this the protocol, "the sequence in which procedures can be called."7
For example, imagine some data from a web-form for the fields of a Model object _and_ some of its related Model objects. Highly normalized Person and Address comes to mind. So, represented as a dictionary/tree you would have something like:
Person =>
first_name => "Bob"
last_name => "Jones"
Address =>
street => "123 Main St."
city => "New York"
...
I wrote an implementation to populate Model objects from this, handling foreign-keys for the relations, etc. Naturally, it's a bit more than 10 lines of code, and wants to be factored into a set of helper functions.
And, naturally, there is some warped Model object that wants to mess with the algorithm. So, I'd like to declare what pieces of this algorithm are available (and reliable) for overriding. We already do this, of course, in documentation. Which is fine, and is the evidence for this last distinction of "algorithm interface."
Documentation and code have a hard time staying in sync, so some language support would be nice (at least annotations). Something like annotations in the interface-method, saying "these helpers can be tweaked, they are used in this way." Essentially a description of the method. Forcing strategies (functional style) can help express this too8.
I would like to program more using these distinctions, but most languages make it gruesome (Java, I'm looking at you). What disciplines/strategies have you used in a language to gain the benefit of these distinctions?
[1] Go look it up. What? Am I your research assistant?
[2] So says Steimann.
http://onward-conference.org/files/steimannessay.pdf
[3] Complexity could be another characteristic of the interface, or any other trait about behavior.
[4] Steimann
2 p. 8, footnotes 13 and 14. See "Gautheir & Pont," and, of course Parnas.
[5] And, perhaps, like StdC++Lib, complexity descriptions.
[6] This all applies to the polymorphic methods that are the default implementations mentioned above.
[7] Steimann
2, p.8 footnote 14.
[8] I read some blog entry on this, which I now can't find.