27

I am reading Head First Object Oriented Design to get a better understanding of OOP concepts.

Polymorphism is explained as:

Airplane plane = new Airplane();
Airplane plane = new Jet();
Airplane plane = new Rocket();

You can write code that works on the superclass, like an airplane, but will work with any of the subclasses. :- * Hmmm.. ..I got this one.*.

It further explains:

-> So how does polymorphism makes code flexible?

Well, if you need new functionality, you could write a new subclass of AirPlane. But since your code uses the superclass, your new class will work without any changes to the rest of your code.

Now I am not getting it. I need to create a sublass of an airplane. For example: I create a class, Randomflyer. To use it I will have to create its object. So I will use:

Airplane plane = new Randomflyer();

I am not getting it. Even I would have created an object of a subclasses directly. Still I don't see a need to change my code anywhere when I will add a new subclass. How does using a superclass save me from making extra changes to the rest of my code?

Kromster
  • 7,181
  • 7
  • 63
  • 111
Abhijeet Panwar
  • 1,837
  • 3
  • 26
  • 48
  • 3
    Because everywhere else you are using `Airplane plane` to refer to the sub class object, regardless of the actual implementing class. Those references will still compile. – AllTooSir Aug 07 '14 at 12:12
  • 3
    possible duplicate of [Try to describe polymorphism as easy as you can](http://stackoverflow.com/questions/210460/try-to-describe-polymorphism-as-easy-as-you-can) – DavidPostill Aug 07 '14 at 12:19
  • 11
    Think you have a `Hangar` class with a `put(Airplane plane)` method. The fact that you added a new plane to your fleet doesn't affect `Hangar` (aka you don't have to add the method `put(RandomFlyer plane)` to `Hangar`, so "the rest of your code didn't change"). – Narmer Aug 07 '14 at 12:20
  • 2
    See also http://stackoverflow.com/questions/1031273/what-is-polymorphism?rq=1 – DavidPostill Aug 07 '14 at 12:21
  • @Narmer: Your comment helped me to understand :) Thanks for that :) – Abhijeet Panwar Aug 07 '14 at 17:58
  • 2
    @AbhijeetPanwar Please note that this is not a silver bullet. It is notoriously difficult to create elegant and useful hierarchies like this. Airplane <- Jet is useful for understanding the mechanism, but not indicative of the design challenge. See for instance Liskow's substitution principle. – Captain Giraffe Aug 07 '14 at 18:55

11 Answers11

29

Say you have the following (simplified):

Airplane plane = new MyAirplane();

Then you do all sorts of things with it:

List<Airplane> formation = ...
// superclass is important especially if working with collections
formation.add(plane);
// ...
plane.flyStraight();
plane.crashTest();
// ... insert some other thousand lines of code that use plane

Thing is. When you suddenly decide to change your plane to

Airplane plane = new PterdodactylSuperJet();

all your other code I wrote above will just work (differently, of course) because the other code relies on the interface (read:public methods) provided by the general Airplane class, and not from the actual implementation you provide at the beginning. In this way, you can pass on different implementations without altering your other code.

If you hadn't used an Airplane superclass and just written MyAirplane and PterdodactylSuperJet in the sense that you replace

MyAriplane plane = new MyAirplane();

with

PterdodactylSuperJet plane = new PterdodactylSuperJet();

then you have a point: the rest of your code may still work. But that just happens to work, because you wrote the same interface (public methods) in both classes, on purpose. Should you (or some other dev) change the interface in one class, moving back and forth between airplane classes will render your code unusable.

Edit

By on purpose I mean that you specifically implement methods with the same signatures in both MyAirplane and PterodactylSuperJet in order for your code to run correctly with both. If you or someone else change the interface of one class, your flexibility is broken.

Example. Say you don't have the Airplane superclass and another unsuspecting dev modifies the method

public void flyStraight()

in MyAirplane to

public void flyStraight (int speed)

and assume your plane variable is of type MyAirplane. Then the big code would need some modifications; assume that's needed anyway. Thing is, if you move back to a PterodactylSuperJet (e.g. to test it, compare it, a plethora of reasons), your code won't run. Whygodwhy. Because you need to provide PterodactylSuperJet with the method flyStraight(int speed) you didn't write. You can do that, you can repair, that's alright.

That's an easy scenario. But what if

  • This problem bites you in the ass a year after the innocent modification? You might even forget why you did that in the first place.
  • Not one, but a ton of modificatios had occurred that you can't keep track of? Even if you can keep track, you need to get the new class up to speed. Almost never easy and definitely never pleasant.
  • Instead of two plane classes you have a hundred?
  • Any linear (or not) combination of the above?

If you had written an Airplane superclass and made each subclass override its relevant methods, then by changing flyStraight() to flyStraight(int) in Airplane you would be compelled to adapt all subclasses accordingly, thus keeping consistency. Flexibility will therefore not be altered.

End edit

That's why a superclass stays as some kind of "daddy" in the sense that if someone modifies its interface, all subclasses will follow, hence your code will be more flexible.

webuster
  • 2,490
  • 18
  • 27
  • See also http://stackoverflow.com/questions/383947/what-does-it-mean-to-program-to-an-interface – Raedwald Aug 07 '14 at 12:43
  • 18
    PterodactylSuperJet! Where can we get one?! – CLo Aug 07 '14 at 13:21
  • 3
    Put a jet engine on a flying dinosaur. Oh wait, a rock fell and they're dead. Only some code can bring them back, poor fellows. – webuster Aug 07 '14 at 13:29
  • @webuster My understanding is that [chicken](http://www.newscientist.com/article/dn13772-t-rex-kinship-with-chickens-confirmed.html) tastes like dinosaur. – Elliott Frisch Aug 07 '14 at 14:56
  • @webuster: Thanks for the answer.But I could'd undestand 'on purpose. Should you (or some other dev) change the interface in one class, moving back and forth between airplane classes will render your code unusable.' part.It will be very helpfull if you can explain it in more detail – Abhijeet Panwar Aug 07 '14 at 17:56
  • "On purpose" means that in order for your big code to run, both `MyAirplane` and `PterdodactylSuperJet` need to expose the exact same method signatures you use in your code. – webuster Aug 08 '14 at 07:09
  • Edited my answer with some more clarifications. – webuster Aug 08 '14 at 07:29
  • Has to be said...Polymorphism and inheritance are orthogonal concepts. They're only linked in most languages because static typing runs into all kinds of issues if they're separate. Dynamically typed languages generally take advantage of "duck typing" -- basically, polymorphism unchained from inheritance. – cHao Aug 08 '14 at 14:40
12

A very simple use-case to demonstrate the benefit of polymorphism is batch processing of a list of objects without really bothering about its type (i.e. delegating this responsibility to each concrete type). This helps performing abstract operations consistently on a collection of objects.

Let's say you want to implement a simulated flight program, where you would want to fly each and every type of plane that's present in your list. You simply call

for (AirPlane p : airPlanes) {
    p.fly();
}

Each plane knows how to fly itself and you don't need to bother about the type of the planes while making this call. This uniformity in the behaviour of the objects is what polymorphism gives you.

Debasis
  • 3,680
  • 1
  • 20
  • 23
7

Suppose you have methods in your Controller class of Planes like

parkPlane(Airplane plane)

and

servicePlane(Airplane plane)

implemented in your program. It will not BREAK your code. I mean, it need not to change as long as it accepts arguments as AirPlane.

Because it will accept any Airplane despite of actual type, flyer, highflyr, fighter, etc.

Also, in a collection:

List<Airplane> plane; // Will take all your planes.

The following example will clear your understanding.


interface Airplane{
    parkPlane();
    servicePlane();
}

Now your have a fighter plane that implements it, so

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

The same thing for HighFlyer and other clasess:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

Now think your controller classes using AirPlane several times,

Suppose your Controller class is AirPort like below,

public Class AirPort{ 

AirPlane plane;

public AirPlane getAirPlane() {
    return airPlane;
}

public void setAirPlane(AirPlane airPlane) {
    this.airPlane = airPlane;
 }

}

here magic comes as Polymorphism makes your code more flexible because,

you may make your new AirPlane type instances as many as you want and you are not changing

code of AirPort class.

you can set AirPlane instance as you like (Thats called dependency Intection too)..

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

Now think of If you create new type of plane, or you remove any type of Plane does it make difference to your AirPort?

No, Because we can say the The AirPort class refers the AirPlane polymorphically.

Sanjay Rabari
  • 2,091
  • 1
  • 17
  • 32
7

Other people have more fully addressed your questions about polymorphism in general, but I want to respond to one specific piece:

I am not getting it, even I would have create an object of subclasses directly.

This is actually a big deal, and people go to a lot of effort to avoid doing this. If you crack open something like the Gang of Four, there are a bunch of patterns dedicated to avoiding just this issue.

The main approach is called the Factory pattern. That looks something like this:

AirplaneFactory factory = new AirplaneFactory();

Airplane planeOne = factory.buildAirplane();
Airplane planeTwo = factory.buildJet();
Airplane planeThree = factory.buildRocket();

This gives you more flexibility by abstracting away the instantiation of the object. You might imagine a situation like this: your company starts off primarily building Jets, so your factory has a buildDefault() method that looks like:

public Airplane buildDefault() {
    return new Jet();
}

One day, your boss comes up to you and tells you that the business has changed. What people really want these days are Rockets -- Jets are a thing of the past.

Without the AirplaneFactory, you'd have to go through your code and replace possibly dozens of calls to new Jet() with new Rocket(). With the Factory pattern, you can just make a change like:

public Airplane buildDefault() {
    return new Rocket();
}

and so the scope of the change is dramatically reduced. And since you've been coding to the interface Airplane rather than the concrete type Jet or Rocket, this is the only change you need to make.

Patrick Collins
  • 10,306
  • 5
  • 30
  • 69
6

As far as I understand, the advantage is that, for example, in a airplane combat game, you have to update all airplanes' positions at every loop, but you have several different airplanes. Let's say you have:

  • MiG-21
  • Waco 10
  • Mitsubishi Zero
  • Eclipse 500
  • Mirage

You don't want to have to update their movements and positions in separate like this:

Mig21 mig = new Mig21();
mig.move();
Waco waco = new Waco();
waco.move();
Mitsubishi mit = new Mitsubishi();
mit.move();
...

You want to have a superclass that can take any of this subclasses (Airplane) and update all in a loop:

airplaneList.append(new Mig21());
airplaneList.append(new Waco());
airplaneList.append(new Mitsubishi());
...
for(Airplane airplane : airplanesList)
    airplane.move()

This makes your code a lot simpler.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
t.pimentel
  • 1,465
  • 3
  • 17
  • 24
  • 2
    -1 It's like saying you should have 'Red' as a sub-class of 'Colour'. This is almost exactly the worst way of writing such simulations. Mostly the differences between such planes are in quantity rather than quality (e.g. mass, weapons carried, look-up tables for SEP etc), so should not be coded as polymorphic behaviour. – Pete Kirkham Aug 07 '14 at 18:16
5

You are completely correct that sub-classes are only useful to those who instantiate them. This was summed up well by Rich Hickey:

...any new class is itself an island; unusable by any existing code written by anyone, anywhere. So consider throwing the baby out with the bath water.

It is still possible to use an object which has been instantiated somewhere else. As a trivial example of this, any method which accepts an argument of type "Object" will probably be given an instance of a sub-class.

There is another problem though, which is much more subtle. In general a sub-class (like Jet) will not work in place of a parent class (like Airplane). Assuming that sub-classes are interchangable with parent classes is the cause of a huge number of bugs.

This property of interchangability is known as the Liskov Substitution Principle, and was originally formulated as:

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.

In the context of your example, T is the Airplane class, S is the Jet class, x are the Airplane instances and y are the Jet instances.

The "properties" q are the the results of the instances' methods, the contents of their properties, the results of passing them to other operators or methods, etc. We can think of "provable" as meaning "observable"; ie. it doesn't matter if two objects are implemented differently, if there is no difference in their results. Likewise it doesn't matter if two objects will behave differently after an infinite loop, since that code can never be reached.

Defining Jet as a sub-class of Airplane is a trivial matter of syntax: Jet's declaration must contain the extends Airplane tokens and there mustn't be a final token in the declaration of Airplane. It is trivial for the compiler to check that objects obey the rules of sub-classing. However, this doesn't tell us whether Jet is a sub-type of Airplane; ie. whether a Jet can be used in place of an Airplane. Java will allow it, but that doesn't mean it will work.

One way we can make Jet a sub-type of Airplane is to have Jet be an empty class; all of its behaviour comes from Airplane. However, even this trivial solution is problematic: an Airplane and a trivial Jet will behave differently when passed to the instanceof operator. Hence we need to inspect all of the code which uses Airplane to make sure that there are no instanceof calls. Of course, this goes completely against the ideas of encapsulation and modularity; there's no way we can inspect code which may not even exist yet!

Normally we want to sub-class in order to do something differently to the superclass. In this case, we have to make sure that none of these differences is observable to any code using Airplane. This is even more difficult than syntactically checking for instanceof; we need to know what all of that code does.

That's impossible due to Rice's Theorem, hence there's no way to check sub-typing automatically, and hence the amount of bugs it causes.

For these reasons, many see sub-class polymorphism as an anti-pattern. There are other forms of polymorphism which don't suffer these problems though, for example "parameteric polymorphism" (referred to as "generics" in Java).

Liskov Substitution Principle

Comparison between sub-classing and sub-typing

Parameteric polymorphism

Arguments against sub-classing

Rice's theorem

Warbo
  • 2,611
  • 1
  • 29
  • 23
  • If you're using `instanceof`, frankly, you're already Doing It Wrong, and no one should care if your code breaks all to hell. – cHao Aug 24 '14 at 08:11
  • @cHao I agree, but instanceof is just the most obvious, generic example of breaking Liskov. The "sub-classing and sub-typing" link shows a more specific, subtle example with Bags and Sets, which follows best-practices, passes type-checking, is unit tested but still breaks :) – Warbo Aug 25 '14 at 14:31
4

One good example of when polymorphism is useful:

Let us say you have abstract class Animal, which defines methods and such common to all animals, such as makeNoise()

You then could extend it with subclasses such as Dog, Cat, Tiger.

Each of these animals overrides the methods of the abstract class, such as makeNoise(), to make these behaviors specific to their class. This is good because obiously each animal makes a different noise.

Here is one example where polymorphism is a great thing: collections.

Lets say I have an ArrayList<Animal> animals, and it is full of several different animals.

Polymorphism makes this code possible:

for(Animal a: animals) 
{
    a.makeNoise();
}

Because we know that each subclass has a makeNoise() method, we can trust that this will cause each animal object to call their specific version of makeNoise() (e.g. the dog barks, the cat meows, the cow moos, all without you ever even having to worry about which animal does what.)

Another advantage is apparent when working with a team on a project. Let's say another developer added several new animals without ever telling you, and you have a collection of animals which now has some of these new animal types (which you dont even know exist!). You can still call the makeNoise() method (or any other method in the animal superclass) and trust that each type of animal will know what to do.

The nice thing about this animal superclass is that you can a extend a superclass and make as many new animal types as you want, without changing ANYTHING in the superclass, or breaking any code.

Remember the golden rule of polymorphism. You can use a subclass anywhere a superclass type object is expected.

For example:

Animal animal = new Dog;

It takes a while to learn to think polymorphically, but once you learn your code will improve a lot.

Bassinator
  • 1,682
  • 3
  • 23
  • 50
3

Polymorphism stems from inheritance. The whole idea is that you have a general base class and more specific derived classes. You can then write code that works with the base class... and polymorphims makes your code not only work with the base class, but all derived classes.

If you decide to have your super class have a method, say getPlaneEngineType(), and you make a new child class "Jet which inherits from Plane". Plane jet = new Jet() will/can still access the superclass's getPlaneEngineType. While you could still write your own getJetEngineType() to basically override the superclass's method with a super call, This means you can write code that will work with ANY "plane", not just with Plane or Jet or BigFlyer.

Adam
  • 2,422
  • 18
  • 29
3

Polymorphism is powerful given that when there's a need to change a behavior you can change it by overriding a method.

Your superclass inherits its properties and behaviors to your subclasses extended by it. Thus it is safe to implicitly cast an object whose type is also from its superclass. Those common methods to your subclasses make them useful to implement an API. With that, polymorphism gives you the ability to extend a functionality of your code.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
3

I don't think that's a good example, since it appears to confuse ontology and polymorphism.

You have to ask yourself, what aspect of the behaviour of a 'Jet' is different from an 'Airplane' that would justify complicating the software to model it with a different sub-type? The book's preview cuts off after one page into the example, but there doesn't seem any rationale to the design. Always ask yourself if there is a difference in behaviour rather than just adding classes to categorise things - usually that's better done with a property value or composing strategies than with sub-classes.

An example (simplified from a major project I lead in the early noughties) would be that an Aeroplane is final but has various properties of abstract types, one of which is the engine. There are various ways of calculating the thrust and fuel use of an engine - for fast jets bi-cubic interpolation table of values of thrust and fuel rate against Mach and throttle (and pressure and humidity sometimes), for Rockets the table method but does not require compensation for stalling the air at the engine intake; for props a simpler parametrised 'bootstrap' equation can be used. So you would have three classes of AbstractAeroEngine - JetEngine, RocketEngine and BootstrapEngine which would have implementations of methods which returned thrust and fuel use rate given a throttle setting and the current Mach number. (you should almost never sub-type a non-abstract type)

Note that the differences between the types of AbstractAeroEngine, although related to the different real world engines, are entirely differences in the how the software calculates the engine's thrust and fuel use - you are not constructing an ontology of classes which describe a view of the real world, but specialising the operations performed in the software to suit specific use cases.

How does using a superclass save me from making extra changes to rest of my code?

As all your engine calculations are polymorphic, it means that when you create an aeroplane, you can bolt on whatever engine thrust calculation suits it. If you find you have to cater for another method of calculating the thrust (as we did, several times) then you can add another sub-type of AeroEngine - as long as the implementation it supplies provides the trust and fuel rate, then the rest of the system doesn't care about the internal differences - the AeroPlane class will still ask its engine for the thrust. The aeroplane only cares that it has an engine which it can use the same way as any other engine, only the creation code has to know the type of the engine to bolt onto it, and the implementation of ScramJetEngine only cares about supersonic jet calculations - the parts of AeroPlane which calculate lift and drag, and the strategy for flying it don't have to change.

Pete Kirkham
  • 48,893
  • 5
  • 92
  • 171
  • 1
    This example uses unnecessary technical terms and hides your answer within the details. Please consider editing. – Bassinator Aug 08 '14 at 11:55
1

Polymorphism gains properties and all behaviors and interfaces of the super class. So is the behavior of a plane really the same as a jet?