8 ms·
Okay, having read the article, I think it goes too far. Inheritance hierarchies have their issues, and raganwald touches on them, but there's a strawman argume
by jdlshore 13y ago
Okay, having read the article, I think it goes too far.
Inheritance hierarchies have their issues, and raganwald touches on them, but there's a strawman argument here.
(Incidentally, raganwald, I've noticed this about all your OO articles. You seem to have a bias against class-based design. It's causing your essays to be less brilliant than they could be.)
Fundamentally, you can think of inheritance as a special case of composition. It's composition combined with automatic delegation.
In other words, if you have A with method foo() and B with method bar(), "A extends B" is equivalent [1] to "A encapsulates an instance of B and exposes 'function bar() { return this._b.bar(); }'."
This is very useful when you want polymorphism. Writing those delegators is a pain in the butt.
More importantly, it tells us how to use inheritance safely. Only use inheritance when 1) you want to automatically expose all superclass methods, and 2) don't access superclass variables.
Now, JavaScript does have the specific problem that you can accidentally overwrite your superclass's variables, and that's worth talking about. But I think that saying "inheritance is bad" goes too far. The article would be stronger if talked about when inheritance is genuinely useful, the problems it causes, and how to avoid them.
Edit: In particular, I want to see more about polymorphism. Polymorphism is OOP's secret superpower. Edit 2: I'm not saying polymorphism requires inheritance.
[1] Not quite equivalent.
- glenjamin 13y agoPolymorphism can be acheived without using inheritance. See Clojure's Protocols or Haskell's type classes for examples of this.
- oinksoft 13y agoSomebody has also made a JavaScript library for this: https://github.com/Gozala/protocol https://github.com/Gozala/protocol
- dllthomas 13y agoTwo points here regarding Haskell. First, a function with typeclass constraints is less polymorphic (in that it will operate on fewer types) than polymorphic function without typeclass constraints. Of course, [the fully polymorphic version is] also more limited in how it interacts with the corresponding values. Second, parametric polymorphism in Haskell is statically resolved. You can have polymorphic functions, but any given container still contains a single type. You can still do dynamic polymorphism in Haskell (by storing a list of records of functions, rather than storing data directly) but it doesn't typically involve type classes.
- gsg 13y ago> Of course, it's also more limited in how it interacts with the corresponding values. This is backwards. Polymorphic values without constraints admit almost no operations at all - you can "copy" them, that's it. This is true to the point that (save diverging) given f :: a -> a, f can only have one meaning. Invariance of sequence elements is also far less problematic in ML-family languages because they have sums.
- dllthomas 13y ago"It" here being the fully polymorphic version. Rereading, it does seem misleading (or at best ambiguous) so I've expanded the pronoun.
- thinkpad20 13y agoI haven't really explored this area of Haskell, but I think there are certain cases where this is possible. For example, I think there might be a way to have a list of tuples of `forall a. [(a -> b, a)]`, where a's type can vary, but applying the first element of the tuple to the second will always produce a `b`. I'm not sure if this is actually the case but it seems (theoretically) possible, and certainly would be convenient. More experienced Haskellers feel free to chime in...
- tel 13y agoYeah, that's certainly possible. It's also largely frowned upon because it's usually over complex. For instance, in your example [exists a . (a -> b, a)] is literally completely equivalent to [b] as the types ensure there is no other thing that can be done with those pairs. The convenience factor is thus almost never the case. There are some nice theoretical properties and a great embedding of OO in Haskell via existential typing [0], but it should rarely be used. [0] http://www.cs.ox.ac.uk/jeremy.gibbons/publications/adt.pdf http://www.cs.ox.ac.uk/jeremy.gibbons/publications/adt.pdf
- deleted 13y ago[deleted]
- SideburnsOfDoom 13y agoI think that part of the problem is that while JavaScript can do OO somewhat, it is not fundamentally an OO language, and if OO is the first tool that you reach for, you are likely doing JavsScript poorly.
- cwp 13y agoJavascript is fundamentally an object-oriented language, it's just prototype-based rather than class-based.
- SideburnsOfDoom 13y agoIt seems more like a functional language to me, but YMMV: http://stackoverflow.com/a/501053/5599 http://stackoverflow.com/a/501053/5599
- kazagistar 13y agoYou seem to be under the impression that those are somehow mutually exclusive.
- SideburnsOfDoom 13y agoI'm sorry if you though so. I am not under that impression. However as mentioned elsewhere ( https://news.ycombinator.com/item?id=7500280 https://news.ycombinator.com/item?id=7500280 ) if most of what you do to organise your code involves passing functions to functions and very little of it involves creating class hierarchies or prototype chains, it's a fair assessment that the language that you are using is more functional than OO. The language that you are using may be a subset of the whole language, but with JavaScript that's given - you have to find the good parts or go mad trying. I was wrong about JavaScript as a whole, but maybe less so about JavaScript as it is successfully used.
- brianpgordon 13y agoWe do something like classes in JavaScript without touching prototypes, and it works pretty well as a natural way of organizing and encapsulating code. Private members are variables/functions defined within the constructor's closure. Public members are properties added to "this" by the constructor. Mixins can be done by calling another class's constructor on yourself. The point is that class-based OO can be trivially imposed on JavaScript objects without abandoning the native object construction mechanism like ember.js does. In fact, CoffeeScript does this in order to implement its own classes.
- xixixao 13y ago"Unlike method invocation, inheritance violates encapsulation" Item 16 of Effective Java 2nd Edition. To the original article, I would improve the wording so that it is clear whether the author is against class hierarchies in Javascript or classes in Javascript. It is not clear to me. One practical example: In Javascript (CoffeeScript), null values can propagate for a very long time. Calling a non-existent method throws an error immediately, while using a nonexistent field (because it changed in the super class) is not that easy to track down. From my CoffeeScript experience, almost any inheritance brought us a headache when we rapidly iterated on our code - but that doesn't mean that CS class construct isn't useful in defining recipes for well-encapsulated objects.
- a3n 13y agoAside: Your invocation of Effective Java made me look for Effective Javascript, and it does exist. Amazon users give it five stars: http://www.amazon.com/Effective-JavaScript-Specific-Software-Development/dp/0321812182 http://www.amazon.com/Effective-JavaScript-Specific-Software... Can anyone comment on how well this books fulfills the expectations implicit in a book calling itself "Effective X?" Or just how effective the book is with respect to accepted javascript practice?
- dangoor 13y agoI haven't personally read it (shame on me!) David Herman and I spoke from time-to-time while I was at Mozilla and he's very smart, a very clear communicator and he knows the language as only a language lawyer can. He's actually a member of TC-39 (the ECMAScript committee) and had a big hand in the modules coming in ES6. By every account I've seen, it's a great book and the only reason I haven't read it is that I've already been bitten by all of JS' pitfalls once or twice :) The real reason I'm posting a comment, though, is to give you a link to the JS Jabber episode in which they talk to Dave about the book: http://javascriptjabber.com/044-jsj-book-club-effective-javascript-with-david-herman/ http://javascriptjabber.com/044-jsj-book-club-effective-java... That episode will give you a good idea of what the book is like.
- geekingfrog 13y ago
- jonahx 13y ago>> In other words, if you have A with method foo() and B with method bar(), "A extends B" is equivalent [1] to "A encapsulates an instance of B and exposes 'function bar() { return this._b.bar(); }'." Your point would hold only if all subclasses agreed to use only the public interface of their parents. But if you do that, your "inheritance" isn't really classical inheritance any more, it's just something that saves you typing when implementing delegation, like ruby's method_missing. The article is not taking issue with composition + "an easy, terse delegation mechanism." The article is taking issue with actual inheritance: the sharing of private state between class and subclass. Your claim that the two things are equivalent just isn't true.
- jdlshore 13y agoSharing private state is not fundamental to inheritance. That's my point. It's a bad idea and people who use inheritance (should) know better than to do that. That's why I said it was a strawman argument. (It's also a bit hypocritical: raganwald says, "JavaScript does not enforce private state, but it’s easy to write well-encapsulated programs: simply avoid having one object directly manipulate another object’s properties." Somehow he fails to apply the equivalent principle to inheritance.)
- kazagistar 13y agoIf you don't share private state, then why not just make the leap and switch to typeclasses instead? Then you don't have the restriction of having to implement all the interfaces for a datatype in the same compilation unit.
- jonahx 13y agoIt's not a strawman: most people who use inheritance directly access properties defined in a superclass. It's a really, really common problem: in fact, most programmers, and most books, don't consider it a problem at all -- they just consider it "using inheritance." What is happening here is that you are redefining "inheritance" in a new, more restricted way that is not in line with common usage, and then saying "But real inheritance doesn't have these problems...."
- emn13 13y agoYou'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.
- cturner 13y ago> This is very useful when you want polymorphism. > Writing those delegators is a pain in the butt. What's a scenario where you'd be exposing a large number of delegators? Reading this made me think - maybe your class hierarchy needs to be abstracted more deliberately into structs-with-interfaces vs domain logic classes. (It's more likely I just haven't thought about the kinds of problems you are thinking of.)
- thomasahle 13y agoWhat about something like Java's `AbstractList` and `AbstractSet`? These save you a lot of typing when implementing the huge `List` and `Set` interfaces.
- mercurial 13y agoFair point. The problem is solved in Haskell by having default implementation in the typeclass, leaving the programmer with only a few methods to implement (eg, 'equals' is defined in terms of 'unequals' and vice-versa, implement the one you want to get the rest of the typeclass working). That said, that's a fairly rare case. Classes with such a large surface are often a code smell.
- stephen 13y agoAgreed about raganwald's bias; surprised to see such a naive (IMO) "don't use class hierarchies" post from him. Your point about "don't access superclass variables" is also spot-on--I don't see how he misses that "self-enforce not calling other objects' properties" (because the language doesn't do it for you) is really not very different than "self-enforce not calling superclass properties". Per his article, I agree that fragile base classes are a problem, but not every base class is automatically fragile--you can design an API for subclasses (in Java/C# worlds, by being very explicit/thoughtful about what you make private vs protected), just like you design an API for external callers.
- peterashford 13y agoYes! What's wrong in the original article is that the the subclasses have inherited access to all the base classes' internals. There is no reason to allow that at all - the base class should hide it's implementation and allow subclasses to specialise via public methods. One could an interface for this if it was useful - which it would be for a library designer, for example.
- AnimalMuppet 13y agoExcept that you can't do that in JavaScript, which is the language the article is (directly) talking about. But he takes lessons from a language that has no access control to member variables, and tries to apply them to all OO languages. That's the problem with the article, IMHO.
- peterashford 13y agoYeah, I agree. I didn't make that clear and I should have.