Implementation Exposure Through Inheritance

There’s a bit of ugliness with object-oriented development which I keep encountering, so I’m wondering if any of the locals out there have a better solution.

Specifically, my problem is that the inheritance relationship seems to be conflating two purposes in at least Java and CSharp. On the one hand, it is defining a one-way interoperability relationship: that is, a class X that inherits from Y means that wherever you could use a Y before, you can now use X, as well. In Java, this kind of relationship can be seen with BigInteger inheriting from Number: wherever a Number is called for, a BigInteger could be used. On the other hand, there is also the implementation work: a class X that inherits from Y means that class X uses the underlying implementatin of Y. This kind of relationship can be seen with Properties inheriting from Hashtable: Properties is simply choosing to inherit from Hashtable because that’s the underlying implementation.

And the conflation of these two very different purposes is complete: there is no way for X to say “I am meeting all the same contract as Y, but I am using my own implementation.” You can perform the inheritance and override all the methods, which works great until the inherited code contract changes, and now (with no compiler warning) you’ve exposed an underlying method which is built off a completely detached implementation. And that’s completely ignoring the fact that you’re still performing a full initialization on Y, even if you’re not ever going to use its faculties! For code that you control, you can always create IY, the Interface implementation of Y, but what do you do about code you don’t control. For a real work example which I have encountered, what if you wanted to implement your own BigInteger implementation (say, based on GMP?).

There’s also no way to say “I want to use all the methods of X in my implementation, but I don’t want to expose its methods to the outside world.” For a Java implementation interpretation, consider the ability to code a class as if you inherited it, but the underlying class methods all behave as private. The reason one would want to do this is because of encapsulation: what if Properties, for instance, wanted to change to get the free sorting provided by a TreeMap? Unfortunately, making the change to now inherit from TreeMap instead of Hashmap could break existing code: it’s been legal for years to work with Properties as though they were a Hashmap, and now you’re making that an illegal cast. This is likely another run-time bug that will be exposed without any help from the compiler, which means that it is a very dangerous change to be making. Yet this change can be made without any adjustment to the the conceptual entity that is “Properties”: what a properties represents is the same, and even the set of method signatures would be the same, but this new implementation could break code. The inheritance from Hashmap is just an implementation detail, yet it has become a key part of the API.

So how do you manage to keep the benefits of inheritance without exposing your underlying implementation? One solution that’s occurred to me is to ALWAYS have an Interface and a Factory for every class, but that’s a lot of noise to sidestep this programming language issue. There’s got to be a better solution — anyone got one?

This entry was posted in Classic, To Be Categorized and tagged , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

5 Comments

  1. Jules
    Posted February 17, 2007 at 3:44 PM | Permalink

    Use a dynamically typed language? Ruby does not have this problem, for example.

  2. bhurt-aw
    Posted February 18, 2007 at 8:36 AM | Permalink

    The properties/hashtable thing is a classic OO blunder, conflating is-a and has-a relationships. I would argue that a properties class has a hash table, not that a properties class is a hash table. A general rule of thumb- when ever considering object relationships, I try both on, and if both fit (more or less), go with has-a over is-a.

    This is more tricky when there is a one-to-one relationship between objects. A properties object has a hash map, but it doesn’t have more than one, or less than one, it always has exactly one. But, in my experience, turing a 1-1 has-a relationship into an is-a relationship is always a mistake. Always. Yes, the has-a relationship means slightly more coding. You can’t just go add(“foo”, “bar”), you have to go map.add(“foo”, “bar”). And you’re likely to end up with a lot of one-liner functions, of the form public void add(String param, String value) { map.add(param, value); }. Life sucks.

    More generally, the problem is one of nominal (name-based) typing. Many people will say it’s a problem with static type systems, but that’s only because all of the statically typed languages they know use nominal typing. The idea of nominal typing is that a class is of type A if it explicitly says it is of type A in its definition.

    With structural subtyping (aka duck typing), a class is of type A if it implements the same interface as type A, wether the class says it’s of type A or not. If it walks like a duck and quacks like a duck, it’s a duck as far as we’re concerned.

  3. Posted February 18, 2007 at 9:29 AM | Permalink

    Jules:

    Well, if I use Ruby, the advantages I am looking for (e.g. compile-time checks on method signatures) all go away. So, yes, I do not have the problems, but I also do not have any of the advantages. PLUS I lose typo checking on my method calls (list.frist is a popular one for me).

    BHurt:

    I know. I was facing this problem and going wishing for type inference the whole time. And there are those out there who are categorically opposed to inheritance because of these issues — if you want the same method signature, use interfaces. I think this is a bit too far: the Number example is a good one, because you can override one method and get the rest for free.

  4. Posted February 19, 2007 at 6:05 AM | Permalink

    There’s private inheritance in C++, but I suspect you know that already :-). Outside of that, sorry. Maaaaybe you can hack up something involving Java’s class loaders (and whatever the equivalent in C# is), but I suspect that using an interface and factory is probably a ton easier than that kind of craziness!

  5. Posted June 20, 2007 at 5:56 PM | Permalink

    This happens simply because these OO languages do not sufficiently distinguish interface from implementation. You may wish to implement the Hashtable interface, but that you inherit the implementation of Hashtable (ie. reuse it) is an implementation detail of Properties, and should not be exposed to client code. Unfortunately, it is since the subtyping relationship of Properties is clearly visible.

    For this reason, and others, I strongly object to Java/C#-style inheritance on general principle, and believe future languages should enforce interfaces only; code reuse should occur via alternate mechanisms which are not exposed to clients, such as traits in Scala, or forwarding as in systems with first-class messages.

2 Trackbacks

  1. By Enfranchised Mind on August 13, 2007 at 11:10 AM

    Another Person Bit by Java Exposing the Implementation through Inheritance…

    Brian Pontarelli encounters the same ugliness in the Java API that I talked about back in Implementation Exposure Through Inheritance.
    It’s a good read, and goes into more detail than I did about the most glaring and chafing example: Java’s…

  2. By Enfranchised Mind on September 9, 2007 at 5:25 PM

    Use vr.s Reuse, or The Second Derivitive of Programming…

    I think I’ve come to the conclusion that anything Raganwald writes is required reading. Even when I don’t agree with what he’s writing. As it is with this post on abstraction vr.s abbreviation in programming. Well, disagree with is…

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">