Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

A well-know shortcoming of traditional class hierarchies is that they are bad when it comes to model the real world. As an example, trying to represent animals species with classes. There are actually several problems when doing that, but one that I never saw a solution to is when a sub-class "looses" a behavior or properties that was defined in a super-class, like a penguin not being able to fly (there are probably better examples, but that's the first one that comes to my mind, having seen "Madagascar 2" recently).

On the one hand, you don't want to define for every property and behavior some flag that specifies if it is at all present, and check it every time before accessing that behavior or property. You would just like to say that birds can fly, simply and clearly, in the Bird class. But then it would be nice if one could define "exceptions" afterward, without having to use some horrible hacks everywhere. This often happens when a system has been productive for a while. You suddenly find an "exception" that doesn't fit in the original design at all, and you don't want to change a large portion of your code to accommodate it.

So, is there some language or design patterns that can cleanly handle this problem, without requiring major changes to the "super-class", and all the code that uses it? Even if a solution only handle a specific case, several solutions might together form a complete strategy.

[EDIT] Forgot about the Liskov Substitution Principle. That is why you can't do it. Assuming you define "traits/interfaces" for all major "feature groups", you can freely implement traits in different branches of the hierarchy, like the Flying trait could be implemented by Birds, and some special kind of squirrels and fish.

So my question could amount to "How could I un-implement a trait?" If your super-class is a Java Serializable, you have to be one too, even if there is no way for you to serialize your state, for example if you contained a "Socket".

So one way to do it is to always define all your traits in pair from the start: Flying and NotFlying (which would throw UnsupportedOperationExceiption, if not checked against). The Not-trait would not define any new interface, and could be simply checked for. Sounds like a "cheap" solution, in particular if used from the start.

share|improve this question
3  
'without having to use some horrible hacks everywhere': disabling a behaviour IS a horrible hack: it would imply that function save_yourself_from_crashing_airplane(Bird b) { f.fly() } would get a lot more complicated. (as Peter Török said, it violates LSP) –  keppla Nov 15 '11 at 12:17
    
A combination of the Strategy pattern and inheritance might allow you to "compose over" inherited behaviour for specific super types? When you say: " it would be nice if one could define "exceptions" afterward, without having to use some horrible hacks everywhere" do you consider a factory method controlling behaviour hacky? –  StuperUser Nov 15 '11 at 15:06
1  
One could of course just throw a NotSupportedException from Penguin.fly(). –  Felix Dombek Nov 15 '11 at 18:07
    
As far as languages go, you certainly can un-implement a method in a child class. For instance, in Ruby: class Penguin < Bird; undef fly; end;. Whether you should is another question. –  Nathan Long Nov 15 '11 at 22:26
    
This would break liskov principle and arguably the whole point of OOP. –  deadalnix Nov 17 '11 at 8:15

14 Answers 14

up vote 14 down vote accepted

As others have mentioned you would have to go against LSP.

However, it can be argued that a subclass is merely an arbitary extension of a super class. It's a new object in it's own right and the only relation to the super class is that it's used a foundation.

This can make logical sense, rather then saying Penguin is a Bird. Your saying Penguin inherits some subset of behaviour from Bird.

Generally dynamic languages allow you to express this easily, an example using JavaScript follows below:

var Penguin = Object.create(Bird);
Penguin.fly = undefined;
Penguin.swim = function () { ... };

In this particular case, Penguin is actively shadowing the Bird.fly method it inherits by writing a fly property with value undefined to the object.

Now you may say that Penguin cannot be treated as a normal Bird anymore. But as mentioned, in the real world it simply cannot. Because we are modelling Bird as being a flying entity.

The alternative is to not make the broad assumption that Bird's can fly. It would be sensible to have a Bird abstraction which allows all birds to inherit from it, without failure. This means only making assumptions that all subclasses can hold.

Generally the idea of Mixin's apply nicely here. Have a very thin base class, and mix all other behaviour into it.

Example:

// for some value of Object.make
var Penguin = Object.make(
  /* base class: */ Bird,
  /* mixins: */ Swimmer, ...
);
var Hawk = Object.make(
  /* base class: */ Bird,
  /* mixins: */ Flyer, Carnivore, ...
);

If your curious, I have an implementation of Object.make

Addition:

So my question could amount to "How could I un-implement a trait?" If your super-class is a Java Serializable, you have to be one too, even if there is no way for you to serialize your state, for example if you contained a "Socket".

You don't "un-implement" a trait. You simply fix your inheritance hierachy. Either you can fulfill your super classes contract or you shouldn't be pretending your are of that type.

This is where object composition shines.

As an aside, Serializable does not mean everything should be serialized, it only means "state you care about" should be serialized.

You should not be using a "NotX" trait. That's just horrendous code bloat. If a function expects a flying object, it should crash and burn when you give it a mammoth.

share|improve this answer
9  
"in the real world it simply cannot." Yes, it can. A penguin is a bird. The ability to fly is not a property of bird, it is simply a coincidental property of most species of birds. Properties which define birds are "feathered, winged, bipedal, endothermic, egg-laying, vertebrate animals" (Wikipedia) - nothing about flying there. –  pdr Nov 15 '11 at 12:57
2  
@pdr again it depends on your definition of bird. As I was using the term "Bird" I meant the class abstraction we use to represent birds including the method fly. I also mentioned that you can make your class abstraction less specific. Also a penguin is not feathered. –  Raynos Nov 15 '11 at 12:59
2  
@Raynos: Penguins are indeed feathered. Their feathers are quite short and dense, of course. –  Jon Purdy Nov 15 '11 at 14:11
    
@JonPurdy fair enough, I always imagine they had fur. –  Raynos Nov 15 '11 at 14:15
    
+1 in general, and in particular for the "mammoth". LOL! –  Sebastien Diot Nov 15 '11 at 19:24

AFAIK all inheritance based languages are built on the Liskov Substitution Principle. Removing/disabling a base class property in a subclass would clearly violate LSP, so I don't think such a possibility is implemented anywhere. The real world is indeed messy, and can't be precisely modeled by mathematical abstractions.

Some languages provide traits or mixins, precisely to deal with such problems in a more flexible manner.

share|improve this answer
2  
+1 for traits/mixins –  David Nov 15 '11 at 12:34
1  
The LSP is for types, not for classes. –  Jörg W Mittag Nov 15 '11 at 12:49
2  
@PéterTörök: This question wouldn't exist otherwise :-) I can think of two examples from Ruby. Class is a subclass of Module even though Class IS-NOT-A Module. But it still makes sense to be a subclass, since it reuses a lot of the code. OTOH, StringIO IS-A IO, but the two don't have any inheritance relationship (apart from the obvious of both inheriting from Object, of course), because they don't share any code. Classes are for code sharing, types are for describing protocols. IO and StringIO have the same protocol, therefore the same type, but their classes are unrelated. –  Jörg W Mittag Nov 15 '11 at 14:37
1  
@JörgWMittag, OK, now I understand better what you mean. However, to me your first example sounds more like a misuse of inheritance than the expression of some fundamental problem you seem to suggest. Public inheritance IMO should not be used to reuse implementation, only to express subtype relationships (is-a). And the fact that it can be misused doesn't disqualify it - I can't imagine any usable tool from any domain which can't be misused. –  Péter Török Nov 15 '11 at 15:09
2  
To people upvoting this answer: note that this doesn't really answer the question, especially after the edited clarification. I don't think this answer deserves a downvote, because what he says is very true and important to know, but he didn't really answer the question. –  jhocking Nov 15 '11 at 15:38

Fly() is in the first example in: Head First Design Patterns for The Strategy Pattern, and this is a good situation as to why you should "Favour composition over inheritance.".

You could mix composition and inheritance by having supertypes of FlyingBird, FlightlessBird that have the correct behaviour injected by a Factory, that the relevant subtypes e.g. Penguin : FlightlessBird get automatically, and anything else really specific gets handled by the Factory as a matter of course.

share|improve this answer
1  
I mentioned the Decorator pattern in my answer, but the Strategy pattern works pretty well too. –  jhocking Nov 15 '11 at 15:41
    
+1 : exactly what I was about to answer :D –  marco-fiset Nov 15 '11 at 15:53
1  
+1 for "Favor composition over inheritance." However, the necessity of special design patterns to implement composition in statically-typed languages reinforces my bias toward dynamic languages like Ruby. –  Roy Tinker Nov 16 '11 at 2:44

Isn't the real problem that you're assuming Bird has a Fly method? Why not:

class Bird
{
    // features that all birds have
}

class BirdThatCanSwim : Bird
{
    public void Swim() {...};
}

class BirdThatCanFly : Bird
{
    public void Fly() {...};
}


class Penguin : BirdThatCanSwim { }
class Sparrow : BirdThatCanFly { }

Now the obvious problem is multiple inheritance (Duck), so what you really need are interfaces:

interface IBird { }
interface IBirdThatCanSwim : IBird { public void Swim(); }
interface IBirdThatCanFly : IBird { public void Fly(); }
interface IBirdThatCanQuack : IBird { public void Quack(); }

class Duck : BirdThatCanFly, IBirdThatCanSwim, IBirdThatCanQuack
{
    public void Swim() {...};
    public void Quack() {...};
}
share|improve this answer
1  
The problem is that evolution doesn't follow the Liskov Substitution Principle, and does inheritance with feature removal. –  Donal Fellows Nov 25 '11 at 0:05

As someone pointed out above in the comments, penguins are birds, penguins don't fly, ergo not all birds can fly.

So Bird.fly() should not exist or be allowed to not work. I prefer the former.

Having FlyingBird extends Bird have a .fly() method would be correct, of course.

share|improve this answer
    
I agree, Fly should be an interface that bird can implement. It could be implemented as a method as well with default behavior that can be overriden, but a cleaner approach is using an interface. –  Jon Raynor Nov 15 '11 at 22:06

First, YES, any language that allows easy object dynamic modification would allow you to do that. In Ruby, for example, you can easily remove a method.

But as Péter Török said, it would violate LSP.


In this part, I'll forget about LSP, and assume that :

  • Bird is a class with a fly() method
  • Penguin must inherit from Bird
  • Penguin can't fly()
  • I don't care if it is a good design or if it matches real world, as it is the example provided in this question.

You said :

On the one hand, you don't want to define for every property and behavior some flag that specifies if it is at all present, and check it every time before accessing that behavior or property

It looks like what you want is Python's "asking for forgiveness rather than permission"

Just make your Penguin throw an exception or inherit from a NonFlyingBird class that throws an exception (pseudo code) :

class Penguin extends Bird {
     function fly():void {
          throw new Exception("Hey, I'm a penguin, I can't fly !");
     }
}

By the way, whatever you choose : raising an exception or removing a method, in the end, the following code (supposing that your language supports method removal ) :

var bird:Bird = new Penguin();
bird.fly();

will throw a runtime exception.

share|improve this answer
    
"Just make your Penguin throw an exception or inherit from a NonFlyingBird class that throws an exception" That is still a breach of LSP. It is still suggesting that a Penguin can fly, even though its implementation of fly is to fail. There should never be a fly method on Penguin. –  pdr Nov 15 '11 at 12:45
    
@pdr : it is not suggesting that a Penguin can fly, but that it should fly (it's a contract). The exception will tell you that it can't. By the way, I'm not claiming it is a good OOP practice, I'm just giving an answer to a part of the question –  David Nov 15 '11 at 13:05
    
Point is that a Penguin should not be expected to fly just because it is a Bird. If I want to write code that says "If x can fly, do this; else do that." I have to use a try/catch in your version, where I should just be able to ask the object if it can fly (casting or checking method exists). It may be just in the wording, but your answer implies that throwing an exception is compliant with LSP. –  pdr Nov 15 '11 at 13:15
    
@pdr "I have to use a try/catch in your version" --> that's the whole point of asking for forgiveness rather than permission (because even a Duck could have broken its wings and not be able to fly). I'll fix the wording. –  David Nov 15 '11 at 13:19
    
"that's the whole point of asking for forgiveness rather than permission." Yes, except that it allows the framework to throw the same type of exception for any missing method, so Python's "try: except AttributeError:" is exactly equivalent to C#'s "if (X is Y) {} else {}" and instantly recognisable as such. But if you deliberately threw a CannotFlyException to override the default fly() functionality in Bird then it becomes less recognisable. –  pdr Nov 15 '11 at 14:05

The real problem with the fly() example is that the input and the output of the operation is not properly defined. What is required for a bird to fly? And what happens after flying succeeds? The parameter types and return types for the fly() function must have that information. Otherwise your design depends on random side effects and anything can happen. The anything part is what causes the whole problem, the interface is not properly defined and all kinds of implementation is allowed.

So, instead of this:

class Bird {
public:
   virtual void fly()=0;
};

You should have something like this:

   class Bird {
   public:
      virtual float fly(float x) const=0;
   };

Now it explicitly defines the limits of the functionality -- your flying behaviour has only single float to decide -- the distance from the ground, when given the position. Now the whole problem automatically solves itself. A Bird that cannot fly just returns 0.0 from that function, it never leaves the ground. It is correct behaviour for that, and once that one float is decided, you know you have fully implemented the interface.

Real behaviour can be difficult to encode to the types, but that's the only way to specify your interfaces properly.

Edit: I want to clarify one aspect. This float->float version of fly() function is important also because it defines a path. This version means that the one bird cannot magically duplicate itself while it's flying. This is why the parameter is single float - it's the position in the path which the bird takes. If you want more complex paths, then Point2d posinpath(float x); which uses the same x as the fly() function.

share|improve this answer
1  
I quite like your answer. I think it deserves more votes. –  Sebastien Diot Nov 15 '11 at 19:44
2  
Excellent answer. The problem is that the question just waves its hands as to what fly() actually does. Any real implementation of fly would have, at the very least, a destination -- fly(Coordinate destination) which in the case of the penguin, could be overridden to implement {return currentPosition) } –  Chris Cudmore Nov 15 '11 at 20:29

Technically you can do this in pretty much any dynamic/duck typed language (JavaScript, Ruby, Lua, etc.) but it is almost always a really bad idea. Removing methods from a class is a maintenance nightmare, akin to using global variables (ie. you can't tell in one module that the global state hasn't been modified elsewhere).

Good patterns for the problem you described is Decorator or Strategy, designing a component architecture. Basically, rather than removing unneeded behaviors from sub-classes, you build objects by adding the needed behaviors. So to build most birds you'd add the flying component, but don't add that component to your penguins.

share|improve this answer

Peter has mentioned the Liskov Substitution Principle, but I feel that needs explaining.

Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

Thus, if a Bird (object x of type T) can fly (q(x)) then a Penguin (object y of type S) can fly (q(y)), by definition. But that's clearly not the case. There are also other creatures which can fly but aren't of type Bird.

How you deal with this depends on the language. If a language supports multiple inheritance then you should use an abstract class for creatures which can fly; if a language prefers interfaces then that is the solution (and the implementation of fly should be encapsulated rather than inherited); or, if a language supports Duck Typing (no pun intended) then you can just implement a fly method on those classes that can and call it if it's there.

But every property of a superclass should apply to all of its subclasses.

[In response to edit]

Applying a "trait" of CanFly to Bird is no better. It is still suggesting to calling code that all birds can fly.

A trait in the terms you defined it is exactly what Liskov meant when she said "property".

share|improve this answer

Let me start by mentioning (like everyone else) the Liskov Substitution Principle, which explains why you shouldn't do this. However the issue of what you should do is one of design. In some cases it may not be important that Penguin can't actually fly. Maybe you can have Penguin throw InsufficientWingsException when asked to fly, as long as you are clear in the documentation of Bird::fly() that it may throw that for birds that can't fly. Of have a test to see if it really can fly, though that bloats the interface.

The alternative is to restructure your classes. Let's create class "FlyingCreature" (or better an interface, if you are dealing with the language that allows it). "Bird" doesn't inherit from FlyingCreature, but you can create "FlyingBird" that does. Lark, Vulture and Eagle all inherit from FlyingBird. Penguin doesn't. It just inherits from Bird.

It's a bit more complicated than the naive structure, but it has the advantage of being accurate. You will note that all the expected classes are there (Bird) and the user can usually ignore the 'invented' ones (FlyingCreature) if it is not important whether your creature can fly or not.

share|improve this answer

The typical way to handle such a situation is to throw something like an UnsupportedOperationException (Java) resp. NotImplementedException (C#).

share|improve this answer
    
As long as you document this possibility in Bird. –  DJClayworth Nov 15 '11 at 15:21

Many good answers with many comments, but they don't all agree, and I can only choose a single one, so I'll summarize here all the views I agree with.

0) Don't assume "static typing" (I did when I asked, because I do Java almost exclusively). Basically, the problem is very dependent on the type of language one uses.

1) One should separate the type-hierarchy from the code-reuse hierarchy in the design and in one's head, even if they mostly overlap. Generally, use classes for reuse, and interfaces for types.

2) The reason why normally Bird IS-A Fly is because most birds can fly, so it's practical from the code-reuse point-of-view, but saying that Bird IS-A Fly is actually wrong since there is at least one exception (Penguin).

3) In both static and dynamic languages, you could just throw an exception. But this should only be used if it is explicitly declared in the "contract" of the class/interface declaring the functionality, otherwise it is a "breach of contract". It also mean that you now have to be prepared to catch the exception everywhere, so you write more code at the call site, and it's ugly code.

4) In some dynamic languages, it is actually possible to "remove/hide" the functionality of a super-class. If checking for the presence of the functionality is how you check for "IS-A" in that language, then this is an adequate and sensible solution. If on the other hand, the "IS-A" operation is something else that still says your object "should" implement the now missing functionality, then your calling code is going to assume that the functionality is present and call it and crash, so it kinds of amount to throwing an exception.

5) The better alternative is to actually separate the Fly trait from the Bird trait. So a flying bird has to explicitly extend/implement both Bird and Fly/Flying. This is probably the cleanest design, as you don't have to "remove" anything. The one disadvantage is now that almost every bird has to implement both Bird and Fly, so you write more code. The way around this is to have an intermediary class FlyingBird, which implements both Bird and Fly, and represents the common case, but this work-around might be of limited use without multiple inheritance.

6) Another alternative that doesn't require multiple inheritance is to use composition instead of inheritances. Each aspect of an animal is modeled by an independent class, and a concrete Bird is a composition of Bird, and possibly Fly or Swim, ... You get full code-reuse, but have to do one or more additional steps to get at the Flying functionality, when you have a reference of a concrete Bird. Also, the language natural "object IS-A Fly" and "object AS-A(cast) Fly" will not work anymore, so you have to invent you own syntax (some dynamical languages might have a way around this). This might make your code more cumbersome.

7) Define your Fly trait such that it offers a clear way out for something that cannot fly. Fly.getNumberOfWings() could return 0. If Fly.fly(direction, currentPotinion) should return the new position after the flight, than Penguin.fly() could just return the currentPosition without changing it. You might end-up with code that technically work, but there are some caveats. Firstly, some code might not have an obvious "do nothing" behavior. Also, if someone calls x.fly(), they would expect it to do something, even if the comment says fly() is allowed to do nothing. Finally, penguin IS-A Flying would still return true, which might get confusing for the programmer.

8) Do as 5), but use composition to get around cases that would require multiple inheritance. This is the option I would prefer for a static language, as 6) seems more cumbersome (and probably require more memory because we have more objects). A dynamic language might make 6) less cumbersome, but I doubt it would get less cumbersome than 5).

share|improve this answer

Define a default behavior (mark it as virtual) in the base class and override it as necessay. That way every bird can "fly".

Even penguins fly, its gliding across the ice at zero altitude!

The behavior of flying can be overridden as necessary.

Another possibility is having a Fly Interface. Not all birds will implement that interface.

class eagle : bird, IFly
class penguin : bird

Properties cannot be removed, so that's why its important to know what properties are common across all birds. I think that it more a design issue to make sure common properties are implemented at the base level.

share|improve this answer

I think the pattern you're looking for is good old polymorphism. While you might be able to remove an interface from a class in some languages, it's probably not a good idea for the reasons given by Péter Török. In any OO language, though, you can override a method to change its behavior, and that includes doing nothing. To borrow your example, you might provide a Penguin::fly() method that does any of the following:

  • nothing
  • throws an exception
  • calls the Penguin::swim() method instead
  • asserts that the penguin is underwater (they do sort of "fly" through the water)

Properties can be little easier to add and remove if you plan ahead. You can store properties in a map/dictionary/associative array instead of using instance variables. You can use the Factory pattern to produce standard instances of such structures, so a Bird coming from the BirdFactory will always start out with the same set of properties. Objective-C's Key Value Coding is a good example of this sort of thing.

Note: The serious lesson from the comments below is that while overriding to remove a behavior can work, it's not always the best solution. If you find yourself needing to do this in any significant way you should consider that a strong signal that your inheritance graph is flawed. It's not always possible to refactor the classes you inherit from, but when it is that's often the better solution.

Using your Penguin example, one way to refactor would be to separate flying ability from the Bird class. Since not all birds can fly, including a fly() method in Bird was inappropriate and lead directly to the kind of problem you're asking about. So, move the fly() method (and perhaps takeoff() and land()) to an Aviator class or interface (depending on language). This lets you create a FlyingBird class that inherits from both Bird and Aviator (or inherits from Bird and implements Aviator). Penguin can continue to inherit directly from Bird but not Aviator, thus avoiding the problem. Such an arrangement might also make it easy to create classes for other flying things: FlyingFish, FlyingMammal, FlyingMachine, AnnoyingInsect, and so on.

share|improve this answer
2  
-1 for even suggesting calling Penguin::swim(). That violates the principle of least astonishment and will cause maintainence programmers everywhere to curse your name. –  DJClayworth Nov 15 '11 at 15:13
1  
@DJClayworth As the example was on the ridiculous side in the first place, downvoting for violation of inferred behavior of fly() and swim() seems a bit much. But if you really want to look at this seriously, I'd agree that it's more likely that you'd go the other way and implement swim() in terms of fly(). Ducks swim by paddling their feet; penguins swim by flapping their wings. –  William Shakespeare Nov 15 '11 at 15:33
1  
I agree the question was silly, but the trouble is I've seen people do this in real life - use existing calls that "don't really do anything" to implement rare functionality. It really screws up the code, and usually ends with having to write "if (!(myBird instanceof Penguin)) fly();" in many places, while hoping nobody creates an Ostrich class. –  DJClayworth Nov 15 '11 at 15:39
    
The assert is even worse. If I have an array of Birds, which all have the fly() method, I don't want an assertion failure when I call fly() on them. –  DJClayworth Nov 15 '11 at 15:41
    
@DJClayworth Then you'd better make sure your penguins are underwater first, as explicitly documented on the Peguin Class Reference page. If you tell your flock to fly(), you might reasonably expect all the birds to be in the air, so an assertion or exception could be helpful depending on what you're trying to accomplish. A Penguin isa Bird, but can't fly(), so that needs to be dealt with. How you model that depends on real circumstances, and trying to find one correct solution to fit all cases is futile. –  William Shakespeare Nov 15 '11 at 16:10

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.