3 ms·
You're describing a fairly narrow subset of subclassing; notably one that is (as you point out) almost equivalent to composition. I think you're right: this is
by emn13 13y ago
You're describing a fairly narrow subset of subclassing; notably one that is (as you point out) almost equivalent to composition. I think you're right: this is not a bad thing. However, how useful is it really? The kind of API's I find myself wanting to pass-through are generally small and abstract (and where they're not, I really wish they were).
Languages that make this special form of encapsulation easy suffer for it indirectly (I'm thinking esp. of java/C# here). Auto-passthrough allows huge API's that would otherwise be unwieldy. Unfortunately, inheritance isn't enough, and when that happens, the poorly designed api's with huge surface areas encourage really bad hacks. All in all I'm not convinced that api pass-through is really a net win for a language. There are alternatives too, such as mix-ins or extension methods, that would allow you to manually pass through only the smallest truly necessary core, and just re-mixin the extras.
But this is all about the best-case for inheritance. Inheritance in common usage also contains two other less ideal aspects, however: semi-private methods, and virtual methods (method overriding). I'm skeptical either has value. Overriding almost necessarily means tight coupling - you need to understand at some level how the internal state of the superclass works to replace calls (even those that the superclass itself makes!) to methods of the superclass. Protected methods suffer from a similar problem - what kind of api is public enough to allow access by a subclass but not public enough to allow access by a wrapper? Of course, using protected methods makes the previously described problem of discouraging bad design even worse; it makes it even harder to write a wrapper, necessitating inheritance even when that's perhaps not exactly what you want.
Finally, if all you want is polymorphism, you just need interfaces, not implementation inheritance.
I think you're right in pointing out that the OP's post doesn't do OOP's subtlies justice, but beyond interface inheritance, I don't think I care much for other aspects of inheritance.
- jdlshore 13y ago> You're describing a fairly narrow subset of subclassing; notably one that is (as you point out) almost equivalent to composition. I'm not sure why you say this is a narrow subset. I'm describing a way of thinking about inheritance (inheritance is like composition + automatic delegation). That way of thinking can be applied to any subclassing operation, and I think it's instructional to do so. It can help you see what's a good idea and what's not. Let's run down the list. Assume you have a class A with a method foo() and class B with method bar(). The equivalent* of "A extends B" is: function A() { this._b = new B(); // equivalent* to calling superclass constructor } A.prototype.bar = function() { return this._b.bar(); // manually delegate } Now let's say A accesses its superclass's private variables in order to do something. The equivalent* is: A.prototype.foo = function() { return this._b._privateVar * 2; // obviously bad, don't do that! } You wouldn't access the private variables of an object you compose; it's obviously bad form. Don't do it when you inherit, either. Now let's talk overriding the superclass's methods. There are several different ways you could do that. The equivalents* are: // Replace superclass method A.prototype.bar = function() { return "my own thing"; // obviously fine } // Replace superclass method and access superclass state directly A.prototype.bar = function() { return this._b._privateVar * 2; // obviously bad, don't do that! } // Extend superclass method A.prototype.bar = function() { var result = this._b.bar(); // equivalent* to calling superclass method return result * 2; // perfectly okay } // Extend superclass method and access superclass state directly A.prototype.bar = function() { var result = this._b.bar(); // equivalent* to calling superclass method return result * this._b._privateVar; // obviously bad, don't do that! } Don't access the private variables of your superclass (or objects you compose) and you'll be fine. Sure, you'll be in trouble if the superclass changes the semantics of the parent method, but that's true of all functions everywhere. If the semantics of a function you're using changes, your code probably just broke. It doesn't matter if the function is defined in a superclass or not. The one thing that's unique to inheritance is the idea of semi-private ("protected") methods that are only visible to subclasses. I agree that they're something to be used sparingly, but they're no different than any other superclass method in how they should be used and overridden. It's a moot point, though, because JS doesn't have them. *Not exactly equivalent, but close enough for these examples.
- emn13 13y agoI think protected methods are just a bad idea in every case. They don't really offer protection (because a subclass can expose them) and they prevent other forms of composition even where they're more appropriate. If you're open to inheritance, you're necessarily open to composition - making that messy serves no purpose. As to this example: // Replace superclass method A.prototype.bar = function() { return "my own thing"; // obviously fine } This is NOT fine - it's really quite nasty. You're breaking encapsulation by affecting how the internals of B work - calls from B's code to bar() will now fail to work as expected. What you want is to use your bar to outside code, but not affect the encapsulation of the superclass. And note that none of this mitigates the pit-of-complexity that inheritance encourages as described in the post you replied to (i.e. bloated, hard-to-wrap API's). There are inheritance-like techniques that work better and don't have the downsides.
- jdlshore 13y agoI don't agree, but I'm going to let it drop. I just wanted to reply to say "thanks" for engaging in a thoughtful conversation. Somebody's been downvoting thoughtful posts like yours (because they disagree with them, I guess). I wish they'd stop and I wanted to let you know it wasn't me. :-)
- emn13 13y agoHey, thanks for the friendly sign off! A much nicer way to end a conversation. Given the subtly of these issues (what is the right design of such an artificial construct), I guess the only obvious thing is that there's no obviously right answer - let alone that it's easy to explain the pros+contras in a reasonable amount of time on an online forum such as this :-).
- hexagonc 13y ago* Overriding almost necessarily means tight coupling - you need to understand at some level how the internal state of the superclass works to replace calls (even those that the superclass itself makes!) to methods of the superclass.* Your problem seems more with badly constructed public apis rather than overriding itself. The only public methods of a class that should be overridable are those with a simple and well-defined expected behavior. Overriding doesn't have to imply tight coupling. So long as it adheres to some expected contract (e.g., input/output ranges and exception handling), an overridable method should be just as much a black box to the parent class as the subclass. An overridable method with no side-effects is equivalent to an initialization parameter to an encapsulated object. * Protected methods suffer from a similar problem - what kind of api is public enough to allow access by a subclass but not public enough to allow access by a wrapper?* You have to think of the consumers of the class. Consumers that only need to use the class and are satisfied with the publicly available interfaces only need access to the public methods. Consumers that want to change the behavior of the class (often for the benefit of other consumers) will override the protected methods. Protected methods offer a set of extension points to consumers while providing useful default behavior for those that do not need them.