30

A well-known shortcoming of traditional class hierarchies is that they are bad when it comes to modeling 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 "loses" a behavior or property 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).

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, are 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 handles a specific case, several solutions might together form a complete strategy.

After thinking more, I realize I 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".

One way to do it is to always define all your traits in pairs from the start: Flying and NotFlying (which would throw UnsupportedOperationException, 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.

jscs
  • 848
  • 9
  • 17
Sebastien Diot
  • 841
  • 8
  • 13

15 Answers15

29

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.

20

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.

Raynos
  • 8,590
17

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.

StuperUser
  • 6,163
11

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() {...};
}
7

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.

David
  • 2,724
7

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.

alex
  • 2,914
6

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.

tp1
  • 1,932
  • 11
  • 10
4

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.

jhocking
  • 2,631
3

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".

pdr
  • 53,768
2

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.

1

This isn't exactly a programming language, but OWL is a system that supports reasoning and handles these sorts of things.

OWL, being a logical system, does not actually allow you to "un-implement" anything. However, it supports the creation of classes which have the same effect.

Let's say we want to have the rule that "all birds fly" and have some exceptions. We can have a disjoint union that partitions all birds into "Normal" birds and "Exceptional" birds. All normal birds fly. This might be:

DisjointUnion( :Bird :NormalBird :ExceptionalBird )
SubClassOf( :NormalBird :CanFly )

Now given any bird, such as :aPenguin, we can try to determine if it flies. If we can infer that :aPenguin is a :NormalBird, then we know it can fly. If it's an :ExceptionalBird, then we need some other source of truth to tell us if the penguin can fly. As an example, it might be part of a Penguins class:

SubClassOf( :Penguin :ExceptionalBird )
SubClassOf( :Penguin ObjectComplementOf( :CanFly ) )
ClassAssertion( :aPenguin :Penguin )

Even this case isn't quite the same as having exceptions. Instead, we carefully defined the class to admit that it may have exceptions in the first place.

Cort Ammon
  • 11,917
  • 3
  • 26
  • 35
0

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).

Sebastien Diot
  • 841
  • 8
  • 13
0

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.

Jon Raynor
  • 11,773
-1

A useful pattern is to have methods whose semantics are essentially "Do action X if possible", typically combined with a means of querying "Can you do X?" When using this pattern, there is nothing wrong with having a subclass which answers no to the question of "Can you do X" even if all instances of the base class would happen to answer "Yes", unless the base class contract specifies that all instances will answer "Yes".

For example, Drawbridge might be a supertype of Bridge, with the base type not having any defined situation where it was impassible, but including an IsPassible property and a warning that users must allow for the possibility that an attempt to drive over a bridge might fail for reasons not particularly forseen in the Bridge interface. A supertype like DrawBridge might need to override the IsPassible property so that it checks whether some new aspects of state have been modified by the PrepareForArrivalOfLargeShip method, but code which is properly designed to use the Bridge type will interact properly with the PrepareForArrivalOfLargeShip method whether or not it is aware of that method's existence.

supercat
  • 8,629
-2

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.

Caleb
  • 39,298