4 ms·
You can explain is-a using examples that actually make sense to model using inheritance, like a set of game objects that have an interface like: class Game
by haberman 15y ago
You can explain is-a using examples that actually make sense to model using inheritance, like a set of game objects that have an interface like:
class GameObject {
public:
// Called every frame to update the object's logical state.
void Update(GameState* state);
// Called to draw the object.
Draw(GraphicsContext* context);
}
class EnemySpaceship : public GameObject { /* ... */ };
class SpaceDebris : public GameObject { /* ... */ };
The point is to move the discussion into the practical reasons for introducing inheritance, rather than to make it a pointless ontological exercise of whether a Square is-a Rectangle or vice-versa.
- marshray 15y agoIs "Spaceship extends GameObject" really that different than "Duck extends Animal"?
- haberman 15y agoYes, because drawing a bunch of game objects on a screen is something a real program might actually want to do, and therefore opens up opportunities to talk about real-world design tradeoffs. Making a program print "quack" when you call the "speak()" method is something that only happens in textbooks, and since there's no reason to actually do that there's no easy way to discuss the alternatives and why polymorphism is a win.
- fleitz 15y agoSo what you're saying is that Farmville is the ultimate result of reading OO textbooks, and that if they had spent more time on a realistic model such as spaceships and gameobjects that a game such as Knights of the Old Republic could be produced by Zynga?
- wlievens 15y agoThat has nothing to do with what haberman is saying. Most people know games, and a simple game model can be expressed in inheritance terms in such a way that's illustrative and less contrived than conventional tutorial metaphors. And I agree with that.
- repsilat 15y agoWhen people say "Duck extends Animal" is bad they typically mean that it's trying to model real-world relationships rather than computational relationships. The example is meant to demonstrate that inheritance should be based on what inheritance means for your program, to make your codebase "nicer", and not to simply categorise your objects. "Spaceship extends GameObject" exists mostly to facilitate polymorphism and code reuse - virtual function resolution, probably, along with all of the non-virtual data and behaviour associated with every GameObject. A "GameObject" isn't a real thing, it's just a convenience. Out of context, "Duck extends Animal" could obviously do the same things for the same reasons, but that's not really the point of the example. It's implied that the relationship was used because real ducks are real animals, not because Duck inheriting from Animal is a good idea.
- marshray 15y agoI'll buy that. But don't try to convince any OO purists that inheritance "exists mostly to facilitate polymorphism and code reuse". :-)
- billybob 15y ago"It's implied that the relationship was used because real ducks are real animals, not because Duck inheriting from Animal is a good idea." I just do not understand this objection. If you wrote Sim Farm, it's quite possible you'd want Duck to inherit from Bird and Cow and Pig to inherit from Mammal, and both Bird and Mammal to inherit from Animal. In the course of caring for your farm, you have to fix tractors, grow crops, and care for animals. All Animals must be fed and will starve if they don't. Why would you code "needs food" on every individual animal class? Birds can get avian flu, and Mammals can get rabies; the pigs can even get rabies from the dogs. Why would you code "can get rabies" on Dog and Pig? Maybe you don't always want to use Sim Farm as your example for teaching OO, but a lot of working code does model real-world items. Animals and cars and such are no worse than files as objects to model, and have the advantage that non-programmers already know something about them. Hence they can focus on the OO concepts and not on the domain logic.
- fleitz 15y agoMost of the benefits of OO programming come from abstracting the machine / concerns (eg. MVC) rather than modeling the domain. (eg. Cars / Ducks / etc). I know it's just an example but it raises several important questions: Why does the GameObject need to know how to render itself? Why does the GameObject update the GameState which the GameObject is ostensibly also a part of? Does updating the GameState directly affect the ability to separate concerns as to whether the GameState being modified is local vs. remote? When you start modeling domains it leads very easily to situations where you have hardcoded permissions for the Manager class instead of bothering to implement a permissions system. Or you have a CEO class that's a singleton. (Hopefully, your code doesn't have to work at RIM) The code savings from class DeathStar2 : DeathStar { public override FatalFlaw(){} } is going to pale in comparison to class DeathStar2 : Object { acts_as_travelling_salesman has_many :laser_turrets, :max => 1024 has_many :tie_fighters, :max => 2048 } when applied over many many systems and objects. Modeling domains is to modeling concerns as algebra is to calculus. Most things in life despite their appearances are not hierarchical and thus do not fit well when modelled explicitly using a class hierarchy. Showing people how to model a domain is easy but virtually useless, showing people how to model concerns is hard but is where the big payoffs are.
- jeffreymcmanus 15y agoCode savings isn't a reasonable goal of OOP. Information-hiding (in which you take an arbitrarily-complicated method and abstract it behind a class) is.
- fleitz 15y agoWhen you give people inheritance all they see is hierarchies and when you give them information hiding systems all they see is access control. One should not be able to find trade secrets by grepping for "private", nor reproduce a companies org chart by a class inheritance graph.
- wlievens 15y agoModeling a domain right is deceptively hard, and to me is one of the primary skills of our profession. Teaching what OO and inheritance mean is not the same thing as modeling a domain.
- VladRussian 15y ago>rather than to make it a pointless ontological exercise of whether a Square is-a Rectangle or vice-versa it isn't pointless. It is the key point of the design : http://www.objectmentor.com/resources/articles/lsp.pdf http://www.objectmentor.com/resources/articles/lsp.pdf
- joe_the_user 15y agoIt is a key point in design AND it usually feels like a completely pointless and annoying waste of time. Humans have such a marvelous natural facility for attaching a meaning to a sentence that we don't feel like loopholes and contradictions within language are meaningful. Ask the average person "who shaves the barber" in a describing of Russel's paradox and they'll give you a "what was the problem" look rather than any answer at all. see http://en.wikipedia.org/wiki/Barber_paradox http://en.wikipedia.org/wiki/Barber_paradox So squares versus circles doesn't seem like a slightly important problem till you have a variation of it starting out of your debugger. And the other side of this is ... since teasing out these loopholes is really hard and so many exist in potentia, it might really be just as well to leave things confused and correct them as they come up. If you a square class that's not a subclass of a rectangle and your rectangle class has an isSquare method and a toSquare method, well, your code will screwy but you'll have saved thousands of dollars in training fees...
- barrkel 15y agoPlease do tell of the occasion you found squares versus rects etc. staring at you out of the debugger. Because the solution to this problem depends on the behaviour you're trying to get, rather than some absolute solution, so philosophical argument about it always seemed pointless to me. If the type is mutable, then per-instance information shouldn't be part of the class; have an isSquare calculated property or whatever. If the types are immutable, then it's OK to embed it in the class hierarchy. If you must have mutable types but you still want to embed it in a polymorphic hierarchy (most usually because of dynamic dispatch reasons, rather than if-casing logic on isSquare), then add a layer of indirection: have SquareBehaviour vs RectBehaviour, as needed. Whatever your solution demands, there's a way of doing it. What the solution doesn't need - nor even cares about - is the philosophical argument.
- kragen 15y agoThis is in fact more or less the example I suggest using in the article, except that the objects don't have a separate update method.
- Markku 15y agoThis is really not a good example as games have (read should have) entity systems. See e.g. these two links for an introduction. http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/ http://t-machine.org/index.php/2007/09/03/entity-systems-are... http://www.purplepwny.com/blog/?p=215 http://www.purplepwny.com/blog/?p=215 There should be no Draw() in your game object (read entity) base class. Not every entity is rendered. You should have instead a component Renderable. And if it happens to be so that your special snowflake has a Renderable component then you can render it on screen. E.g. AI pathing nodes would not usually have one unless you are debugging them you can add one. In your example you end up with Movable extending GameObject, Camera extending GameObject and then you have no way of combining these two behaviours.
- wlievens 15y agoMy nitpick about EnemySpaceship: whether an entity is enemy or not should not be part of its type but part of its state and/or logic. I know it's "just an example" but to me this thread is about whether arguably incorrect examples are bad practice or not.
- jrockway 15y agoActually, this makes more sense as a trait or interface than as a superclass, especially as you're not even implementing any behavior. It's the same way for squares and rectangles. Square does the Rectangle and Quadrilateral interface, Rectangle does the Rectangle and Quadrilateral interfaces, and it doesn't matter. If you want a subtype/supertype relationship, then you have to diverge from what people intuitively think about classes. Liskov's Substitution Principle says that subtypes must be completely substitutable anywhere a supertype is used, which means a rectangle isa square because a square does setX, getX, and getY, whereas a rectangle does setX, setY, getX, and getY. (This doesn't make much sense because people use inheritance as "copy and paste this crap from the superclass into my subclass" rather than to setup substitutable subtype/supertype relationships.) Interfaces avoid that problem because there is no supertype or subtype, only equal types that agree to have the same interfaces. Then it doesn't matter if a square isa rectangle or not.