To a first approximation, inheritance is always wrong. The Design Patterns book advocates composition -- building up an object out of smaller ones -- over inheritance. Yeah there's more work to delegate messages around, but if you didn't like typing you shouldn't have picked Java (or, use an editor that makes this easier).
Also, when trapped in languages like Java, define behaviour in terms of interfaces rather than inheritance. Interfaces are exactly what raganwald is talking about -- they define the ability of something to respond to methods, but are decoupled from any parent-child relations.
I've used dozens of Ruby gems, plugins and classes over the years that have well-documented shared APIs and none of them (obviously) have had interfaces. Interfaces are good in certain environments for hiding implementation details where class-level contracts are enforced, but in Ruby this would provide no benefit at all.
Think of it this way: since Ruby has no class-level contracts, to some extent everything is already an interface. Everything is abstract. Every Ruby object has the opportunity to re-implement functionality in an abstract way.
You get exactly the same benefits as you would from an interface (minus the rigidity)...
That said, yes, Ruby with interfaces seems kind of pointless and is just throwing more code in, which is the opposite of one of the nicer points of the language.
I looked quickly at pep-3119 and it seems like it's a short-hand way of checking if an object will respond to some set of methods.
Now, I cop to having done type checking in Ruby (either with is_a? or respond_to?) but I always felt it was a code smell, a hack to solve a distraction while I moved on to something else more pressing.
I don't know a whole lot of Python, but I'm guessing that since Python has a more Java-like form of OOP (i.e., method invocation vs. the message passing of Ruby, a la Smalltalk) this kind of type-checking is quite acceptable, even though Python is ostensibly duck-typed.
Am I wrong, and by how much?
Generally, checking the type with isinstance is considered bad practice although it can be useful sometimes (I can't think of any simple examples off the top of my head though).
The main reason I can think of for using an interface or abc is simply so that if you forget to add a required method, the exception gets thrown sooner rather than later. Plus with an abc, you also allow the base class to define methods that depend upon abstract methods (for instance, if you subclass the Mapping abc, you get __contains__, keys, items, etc for adding __getitem__). Strictly speaking, there's no reason why you can't do that without an abstract base class, but it makes it easier.
In short, there are useful reasons to have interfaces and abstract base classes in Python, but they probably aren't the same reasons you'd want them in a statically-typed language like Java.
If you're in a hurry, the main motivations appear to be:
- declaring that a class supports particular functionality without risking the complications of multiple inheritance
- the ability to register adapters from one interface to another means no more calls to isinstance() (or at least, they're abstracted away to a library function)
- Easy to make your test suite check that you haven't forgotten to implement any methods of any of the interfaces your class claims to support.
You can pick out a set of objects that implement a certain interface from a larger set. You can do this for classes, objects, perhaps modules.
Someone already mentioned Twisted plugins. They currently use Zope interfaces. You create a class then declare that it implements a particular interfaces.
Haskell type classes seem, to me, to take the right approach here as well. In fact I would argue their main benefit is not catching programmer error - but encouraging thoughtful design.
In Eclipse it's as simple as using the refactor tool to 'pull up interface' from any God Class of my choosing.
Neither language nor framework can force a coder to apply the Single Responsibility Principle. Coders can be willy-nilly with or without interfaces.
Now, many languages with formal interfaces (e.g., Java) are strongly typed, and they treat interfaces as types. Thus, for example, classes have to declare up front what interfaces they implement. That doesn't fit well with the flexible attitude of a duck-typed language. However, if interfaces were just collections of method signatures that could be tested against objects at runtime, that would be perfectly compatible with duck typing.
Languages like Modula-3 that use structural type equivalence sort of do this even in a strongly-typed system. Sort of.
Interfaces are useful in defining what an implementor must provide, but an example implementation does this as well.
Also, if it helps, just think of the impl as an interface with a default behaviour that should be overriden ;)
If this is too forgiving, then we can even have the impl methods throw errors ("override me!") and simulate an interface.
For example, a hash-based collection might require that all its members implement an equality method and a hash method. Typically in Ruby you test this by just waiting for one of those method calls to blow up somewhere in the hash table code. Instead, you could test "acts_like?(Hashable)" -- meaning "reaponds_to?(:=) && responds_to?(:hash)" -- on insertion. Then you could give a nice, meaningful error message at a nice point in the code.
Note that this has nothing whatsoever to do with is_a?, which is the important difference between this and type-based interfaces as in Java.
What I was trying to get to: if you accept the existence of single-method ducktypes (like "things that have a walk() method" oder "things that have a quack() method"), then by intersecting these concepts, you arrive at things just like interfaces, whether you call them interfaces or not. If you want the pond system to work with your two-legged bird-sound robot, you perform the same mental steps as when implementing an interface. Hence, ducktypes could be seen as interfaces without an instanceof-operation.
What mechanism would enforce the contract anyway? The best you could hope for would be a runtime error thrown when the interpreter detected the incompatibility, and you can do that manually now (I do it frequently to imitate abstract classes).
In other words... Do you like Plato, or do you think Aristotle had it right?
There are lot of cool isomorphisms between philosophy (particularly metaphysics) and software design.
And this describes software: we have a preconceived set of data structures and algorithms, yet we can fit them to all kinds of structures of ad hoc business uses.
We cannot simply draw all the structure from observation. We must impose something of the material which we are modelling with. The whole task of engineering, in general and in each case, is to find a balance, a practical meeting of the two.
This is not really something that OO has 'wrong' that something else can fix. OO has weaknesses, but the deeper 'problem' is never soluble: the essence of engineering design means it is always an imperfect tradeoff.
Kant locates the origin of ideals/forms/categories in the mind only, as an essential pre-existing structure of the mind (think: hard-coded ROM), where Plato located their origins in reincarnate memory (not sure what the computer analogy would be there -- recycling a motherboard at Fry's?).
I think the idea of Forms is still quite valuable
Aristotle takes kind of the opposite tack - he is also very focused on building ontologies and taxonomies, but he views them as constructions of the intellect imposed upon their subjects, rather than being the most fundamental reality of the subject prior to its actual being. It's pretty much the same distinction ragenwald talks about in the OP.
As for the general isomorphism between metaphysics and philosophy, you can find it almost everywhere, depending how hard you want to look (and how far you want to stretch your metaphors...). But metaphysics is largely concerned with the types of things and entities in the world, and how they can possibly interact. If you think of the "software space" as its own universe, it's pretty easy to draw parallels. Forms and Categories are low hanging fruits, obviously, but a few other parallels spring to mind:
- Kant's seperation between Phenomena and Noumena relates to the difference between interface and implementation, as well as to the concept of abstractions in general.
- How a software component's "epistemology" (how it "knows" about other components in the system) works can be compared to different philosophers - do they operate on a consistently readable shared state (an empirical world?) or do they request information from a central broker service (God brokers sense impressions, ala Berkeley?)
- Berkeley's idea of direct impressions only in the mind has some relationship to the concept of laziness.
I'll try to think up some more and post them later. It's less that you can write a paper on the startling isomorphism between theories, and more that it's really easy to use software metaphors to describe philosophy and vice versa (though software tends to be much more concrete, obviously).
Well, not exactly, and in fact this would turn Aristotle into his metaphysical opposite: a nominalist in the mold of Occam.
For Aristotle the formal cause of a substance is in fact absolute, every bit as real and 'prior' as with Plato, the big difference between the two being the question of epistemology, or how we become acquainted with the form to begin with. Yes, Aristotle argues that we know the form of a thing from experience, but the form of that thing, and the abstractions we produce from many experiences from similar things, are as real as with Plato and, as in Plato, point to higher, more organized spiritual structures that comprise ultimate reality.
Call me when somebody comes up with a Whitehead-ean programming paradigm.
I'm not sure Kant would allow that phenomena are all that abstract. They're not eidetic/formal (although later Husserl would argue that they intend towards eidetic objects), they're merely the subjective experience of a particular thing. The (purely hypothetical) relationship between the noumena and phenomena are (naively and hypothetically) one-to-one, if there is any real relationship at all.
The n/p distinction is made by Kant not to emphasize the abstract nature of our mental experiences, but rather to emphasize a radical epistemic uncertainty regarding the objects that we would naively assume "exists" apart from the experience.
Still some parralels can be drawn. A taoist coder would let things happen, let the code find its way spontaneously toward the result, like the knife of the butcher must find its way into the meat. Taoism applied to code would produce independant pieces of software that live by itself and may have no intended usage (e.g. the yes command in coreutils).
A confucean coder would always follow the standards (the rites), always consider his production in context of a bigger picture, would produce a software that is very strict for himself and forgiving for others, would write programs tending to improve the life of others.
It has been said that wise Chinese of the old time where confucean the day and taoist the night. While this is actually because taoism is related with sex, the parallel could be that a good hacker/coder should be confucean at his day work, and taoist for personal projects.
None of them would thing in "ontology", though, because this is something, like the idea of God, that has no deep meaning in Chinese ancient philosophies. They would not debate too much on languages and the meaning of words, they would just use them as fit. They would use OOP as a tool, when it helps writing better code (when one need to glue state and actions together).
Do I contradict myself? I said "no parallels" and then "there are some parallels". But the ones I draw just apply to any activity of any human being, not just coding.
Two modern philosophers that I think may provide a better conceptual foundation for a less reified/Platonic/Aristotelian/Formalist view would be Alfred North Whitehead and Henri Bergson, both of whom described the ingression or duration of individual substances into other substances. Rather than hard borders (interfaces?) that interact in static and unchangeable ways, substances actually change each other at these borders. Bergson in particular I think would be interesting at many levels of the SDLC -- from emergent design (he advocated an 'emergent' fingertip-feel, intuitive approach to Science) to the nature of objects and hierachies.
What about the notion that an interface can change/morph according to the client that consumes it? This would move towards a more Heraclitean/Whiteheadean/Bergsonian paradigm.
I'm not sure I completely followed your ideas about hierarchical classification being more useful for testing... perhaps because using animal categorization as an example was difficult to follow since people don't write tests for animals. What does it mean to have hierarchies for tests and what benefits does it provide?
Usually you start with some data. It might be some loose tuple in a dynamic language like erlang, or an algebraic data type in a language like Haskell (not that there are many like it). It's like saying, I have a Cat or I have a Bat. You haven't said anything about the two yet other than that they are different. You could then add more information, such as the number of legs or color. You add behavior by defining functions that can act on that data and create changes from them. For example, two cats can reproduce, so you define a function that takes two Cats and makes a kitten (new Cat). Bats might also reproduce, so you can write the function more generally to take any two data objects that have the minimum prerequisites (such as identical internal representations) and produces a new one that's a combination.
When a new data type is needed, you simply add it and it automatically gains all this functionality based on its form.
Essentially, OOP seems to want to start from the behaviors and have that define the data. When writing functionally I tend to define the data, then build behavior on top of it which is driven more by the form of the data than any idealized class hierarchy.
I'm not sure I'm explaining this terribly lucidly, but hopefully this can start a discussion among the more articulate posters.
Leaving out changing requirements and DNA mutations for a second, there have been massive changes to the "official" ontology as we discover new species or examine DNA and have to say "oops, turns out, the platypus doesn't derive from birds at all".
This is standard OOP 101 advice. 'Prefer encapsulation to inheritance' has been axiomatic in OO literature for over a decade. GOF and all the GOF-derived texts emphasize this.
OOP <> Inheritance
Inheritance is only one tool in the OOP toolbox, and probably one of the least important. OOP itself is only one conceptual tool in the programmer's toolbox, alongside procedural and functional tools. Learn them all, learn their strengths and weaknesses, and learn how to apply the right tool to the right problem.
What's this about? Some patent or something?
I'd guess this is a reference to Bertrand Meyer's company. Since I learned DbC from Liskov & Guttag's excellent book, which came out before Meyer's, I never had much reason to look into Meyer or Eiffel even though they became practically identified with DbC.
I, personally, think of inheritance as it implies near the end: if A derives from B, A must effectively be B in all B-exposed methods. It can optimize, but it cannot change; A must pass any test B would pass, or it's not actually a B.
But lots of code violates this. I think it's because a lot of people learn inheritance with the real-world connections in mind, and it's frequently taught to beginning programmers as a golden bullet. It can do a lot, but it has subtle surprises until you get better at programming.
Implementation inheritance violates encapsulation. So do getters and setters. They let code outside your object rely on your implementation and make your system brittle.
Programación Orientada a Objetos.
The fundamental problem with OO inheritance, is that it is not ontological or mathematical inheritance! OO inheritance has almost no relationship to thousands of years of what "is-a" means -- i.e. subset. It's backwards - subclasses are extensions, or supersets of their parents.
The way to implement inheritance correctly is "specialization by constraint", i.e. you define a subclass by the constraints it has on its superclass. This is what's happening in the latter part of the article, with design-by-contract and test cases for your various classes.
This is also, by the way, how ontology languages like OWL work, and in part why they're so hard to learn/use -- their notion of inheritance and "subclassing" is the mathematical notion (i.e. more like subset), and the opposite of OO programming languages, so we have 25+ years of history to unlearn.
I suspect the reason OO inheritance took the path it did was for simple expediency on the part of compiler writers & language designers. Specialization by constraint ain't easy to implement declaratively.
Also, writing a subclass definition can be seen as adding constraints (of the form "must also have a bla() method returning an int").
Steve Yegge put it best.
problems of OOP is explained in Go Language video by Rob Pike*, however very intellectually stated arguments in this article are mainly false.
Python, Objective-C, Smalltalk and even C++ in some ways all do this.
I suggest if you feel like the OP does, you use one of them instead of java or C# or whatever that dude is using.