5
$\begingroup$

I am preparing a lesson about polymorphism in C++ and I would like to give an original example other than animals and shapes and I am really struggling with it because I realize polymorphism is not that useful for real concrete applications.

Can someone suggest a good example of polymorphism use?

$\endgroup$
5
  • $\begingroup$ Good question! The best examples would probably be derived from business situations that people would be familiar with. Example: types of plane tickets (economy, upgraded, business, first class). These could have different properties / attributes, but some common methods. Or maybe meal ordering? $\endgroup$
    – Scott Rowe
    Apr 20, 2019 at 23:18
  • 1
    $\begingroup$ Your statement that "polymorphism is not that useful for real concrete applications" is both false and naive. It implies you don't understand what polymorphism is, so it is hard for you to judge what might be a good example. In particular, "animals" is a particularly bad idea until you realize that all of the polymorphism in the Linnaeus taxonomy is interface (not concrete) polymorphism. There are no instantiations of the Mammal class, for example. There are individual tigers and mice, of course, but Mammal is better thought of as an an interface. $\endgroup$
    – Buffy
    Apr 21, 2019 at 12:59
  • $\begingroup$ See related question cseducators.stackexchange.com/q/378/204 $\endgroup$ Apr 21, 2019 at 21:11
  • 1
    $\begingroup$ Good question. While students can easily grasp encapsulation and inheritance, polymorphism often remains an obscure concept, and textbook examples are not any help. $\endgroup$
    – totera
    Apr 22, 2019 at 15:59
  • $\begingroup$ Don't overlook testing! "Polymorphism," in its purest form, means defining an interface that can be implemented by two or more different classes. I have often defined an interface that was implemented by only one class in the finished program, but which was implemented by one or more other classes (a.k.a., test doubles) in the test suite. $\endgroup$ Apr 22, 2019 at 16:24

6 Answers 6

3
$\begingroup$

Let me give some background that will, I hope, change a few minds about polymorphism and how it should be used and when its use should be avoided. The essence of it is that too many of the examples are naive and incorrect. They may serve as metaphors, but are, in practice, unworkable. Let me try to show why and also show how polymorphism can be extremely useful - even essential in OO style programming.

One of the problems is that most OO languages permit things to be built in a way that they should not be built. That isn't special to OO, of course. You can write 20 page functions with if and while statements nested 15 deep in C, of course, though no one can understand or maintain the code. Likewise you can use concrete subclassing in a way that builds equally mis-formed software. One of the first examples of subclassing to appear in print was that a Circle class was a subclass of a Point class. It was done this way because it was easy to do, not because it was sensible. A circle isn't a special kind of point. But to build the Circle class from a Point class you could just add a single field for the radius and use the point coordinates for the center. But it makes no sense.

First, the Animal hierarchy is unsuitable teaching students about polymorphism unless you remember that the entire hierarchy consists of abstractions, not concrete "objects". There are no instantiations of class Mammal, or Vertebrate. Those are merely abstractions. There are Kangaroos, of course, but there are no subclasses of Kangaroo, unless Kangaroo itself is an abstraction.

Likewise Vehicle is a poor vehicle (heh heh) for teaching polymorphism as there are too many variations in the concrete things that carry that description. A bicycle, a golf cart, an automobile, and a tractor-trailer rig are all Vehicles. So is a Sherman Tank. They have very little in common other than mobility and the capability of carrying things. If you try to build a "hierarchy" of such things, the variations will overwhelm the commonalities. They are more "unlike" than they are "like", in fact. Just as for Animal.

The problem, as I see it, is that people try to give examples that are "too big". Top-level design with polymorphism. This is hard to manage and usually fails. In fact, one of my rules for building good software is that any concrete hierarchy that needs to add publicly visible elements in subclasses is fundamentally broken. Why is that?

Why it's broken

In building good software we need to manage complexity. Complexity is what kills us. It makes our programs buggy if not well managed and it also makes them hard to maintain. Suppose that you are at a point in a program in which you, the programmer, have built a structure that required ten thousand decisions to be made and the software didn't hide any of them from you at the current line you are writing. You need to take account of every decision that has been made. It is impossible. It is nearly impossible to maintain more than about seven independent items in your mind. Software is intended, actually, to hide such decisions. It is why that 15 level selection structure is impossible to maintain. Polymorphism is a way to manage complexity if used well, but since it can be used badly it can also add to the complexity burden. So, you need a way to use it well and to teach good usage.

For example, suppose that I have, in Java, a List of Vehicles. Suppose that Vehicle is a superclass and that every subclass of Vehicle has added different public features. The bicycle and tractor-trailer classes know about number of gears, but it is irrelevant for golf carts. All is well until we try to remove items from the List and operate on them. All the program knows about them is that they are vehicles unless either (a) we remember the most specific class of each item externally (mentally) or (b) we provide a way for the program to recapture the most specific class. Since (a) is essentially impossible, Java provides for (b), of course. But having to ask instanceof questions or do some sort of class based switch means that we have completely given up the advantages of polymorphism. We need to recapture the most specific thing when polymorphism is supposed to let us deal with general things. You are back to writing if statements when they shouldn't be needed (if you had done a better job).

How to do better.

Suppose that instead of thinking of using polymorphism at a large scale, think of it at a much smaller scale. Don't try to get some polymorphic sense about vehicle overall, but think about how vehicles are actually built in the real world. I'll take bicycle as a simple example.

I have a lot of bicycles, actually. I have a nice one that is suitable for trail riding and for expeditions and even for the road. How is that. The bike I'm thinking of is composed of parts. It has a seat, wheels, gears, etc. I'll focus on just those. When I first purchased it the bike was set up primarily for moderately comfortable use on mild trails. It worked well for that. But the parts I mentioned are all replaceable. By changing the saddle I can make it better fit my anatomy. By changing the wheels I can make it much nicer on the road - both lighter and with a smoother ride. By changing the gears - even the number of gears I can make it more useful for serious rocky trails or for more modest prepared surfaces. This is polymorphism used well. I can easily swap parts and by giving it one part at one time and a different part at another time I can give it different behavior. It is the behavior that is polymorphic, not the interface. The two sets of wheels I have for this bike are interchangeable, but behave very differently. The saddles enable one use or another, for one rider or another. But the interface between the saddle and the rest of the bike is fixed. And I don't need to remember each time I switch gears whether I have the road wheels or the trail wheels on the bike. It "works" the same, but "behaves" differently without any decisions (if-this-then-that) on my part as I ride. The only time I need to make decisions is when I set up the bike for a ride (or an expedition) and then the bike itself "remembers" all of those decisions and behaves appropriately. I don't have to remember whether I'm on the "expedition bike" or the "fast road" bike. Nor do I have to recapture that knowledge at any point in my ride.

You can build software this way also. Build complex objects with composition - lots of part. Put the polymorphism in the parts. Each kind of part has an interface that doesn't need to be tailored to use (i.e. no added public features in subclasses). When the object of which these polymorphic things are a part needs to change its behavior, swap out one part for another. The overall object's interface didn't change, but its behavior does.

For example, in a Calculator, sometimes pressing the "five" key does one thing (accumulates into the current display value) and sometimes does something different (begins the next value in the computation after an operator key). You can use different objects to handle these two things. The calculator either has an accumulator object or an initiation object attached to the five key. The objects are swapped at appropriate times, but once the object is swapped the calculator itself has different behavior. But, the key idea, is that the accumulator object and the initiation object have exactly the same interface. This means that I, the programmer, don't need to remember which it is, the calculator remembers that, and I the programmer don't need to recapture the specific class of the object attached to the numeric key since I interact with both of them (as a programmer) in exactly the same way.

This is true polymorphism. Other ways of using it are very ad-hoc and require external complexity management that is better handled by the software itself.

This is how automobiles are built these days. You can buy most cars with different engines, different seats, different entertainment systems, etc. But each of those various engines presents the same interface to the rest of the automobile. Therefore they are swappable without modifying the chassis, and by swapping them they change the behavior of the object of which they are a part.


Let me add a simple rule of thumb for OO programmers who might benefit from the guidance.

Think of every addition you make to the public interface of a concrete subclass (even when implementing an interface) as a failure. It is sometimes necessary to do it and even the libraries show evidence of it, but think of every new public feature (in a concrete class) as a failure of design. You are building in the necessity to do ad-hoc decision making in the future. You can probably avoid that by rethinking the design. Every ad-hoc decision you don't need to make in the original build and especially in maintenance is a BIG WIN.

$\endgroup$
6
$\begingroup$

Say you want to present a choice of interactive games

   out.println("Which game (...) ?");
   Game game;
   switch (in.nextLine()) {
   case "HI-LO" : 
         game = new HighLowGame();
         break;
   case "Riddle" : 
         game = new RiddleGame();
         break;
        ...
   }

Here you are. You have polymorphism. The game variable may refer to objects of different types.

Now, how to play a game (in textual form) ?

     out.println(game.getPresentation());
     while(! game.isOver()) {
         out.println(game.getQuestion());
         out.print("> ");
         game.submitAnswer(in.nextLine());
         out.println(game.getMessage());
     }

Polymorphic code. Now what is in a Game?

    interface Game {
        String  getPresentation();
        boolean isOver();
        String getQuestion();
        void submitAnswer(String line);
        String getMessage();
    }

An implementation ?

class RiddleGame implements Game {
    String getPresentation() {
         return "What cheese is made backwards?";
    }
    String getQuestion() {
         return "Guess it!";
    }
    boolean found = false;
    void submitAnswer(String line) {
        found = line.trim().equals("edam");
    }
    boolean isOver() {
        return found;
    }
    String getMessage() {
        return found ? "you got it:" : "no, it isnt";
    }
}

Boring java details omitted.

[rant] Most introductory examples for OOP are utterly silly and totally inadequate to explain that classes are here to offer services. This unfortunate fact leads them to a lot of misconceptions. [/rant]

The purpose of OOP is NOT to build taxonomies of animals, or make container objects with a getter and a setter for each attribute. It is not to model even a simplified view of the real world. It is there to write code which does something.

Oh, well, I see you don't like the RiddeGame because it knows only one joke. So introduce a RiddleProvider as a parameter, whose job is to supply a Riddle (question + answer pair).

You can imagine several kinds of RiddleProviders, so it is itself an interface with different implementations.

The simplest is the AlwaysTheSameRiddleProvider, which only know one joke, just like me. Others can run a predefined sequence, or a random choice, from an array, or from a file. Again, they offer a service to be used by another object.

$\endgroup$
2
  • $\begingroup$ Yes. My Tic Tac Toe example was actually wrapped in a larger framework which included a game (type, players, scores, winner, etc) and players. I was going to add other games, but it served the purpose as a teaching example with one. $\endgroup$
    – Scott Rowe
    Apr 22, 2019 at 10:53
  • $\begingroup$ Thanks for your example. By the way, your rant is well-founded, and even more the following observation. $\endgroup$
    – totera
    Apr 22, 2019 at 16:09
5
$\begingroup$

Almost any popular library such as Boost will have tons and tons of code with polymorphism examples. For instance, look at Polymorphism models write up in Boost. I'm sure, you used them a lot too, e.g. iterators in containers, streams and so on. They're extremely practical

$\endgroup$
4
$\begingroup$

One of the most used examples of polymorphism in real code would be sort. You don't write a comparison sort method for each type of object: you write one comparison sort method and then either implement a comparator for the type or make your objects implement a Comparable interface.

I hope this example convinces you to change your opinion that "polymorphism is not that useful for real concrete applications".

$\endgroup$
4
  • $\begingroup$ While correct, this might be a bit subtle for some folks to grok. It is a question about where polymorphism is most useful. It turns out to be a great way to build small things that are composed to build bigger things. Think of the "sort" as being composed of two things: an algorithm that is agnostic about comparisons and a comparator that is all about that but nothing else. Used this way, polymorphism can be superior to explicit testing to introduce variability into a process. $\endgroup$
    – Buffy
    May 3, 2019 at 11:23
  • $\begingroup$ Many of the classic Design Patterns are based on that idea of composition combined with polymorphism. $\endgroup$
    – Buffy
    May 3, 2019 at 11:54
  • $\begingroup$ @Buffy, whether it is subtle or not is partly down to what language is used to teach. If polymorphism is taught in terms of a basic mental model of parametric types then it's blatant rather than subtle. Chalk one up to SML. $\endgroup$ May 3, 2019 at 12:53
  • $\begingroup$ Actually, I meant subtle for some of the readers here. But thanks for giving me a bit of insight and courage to add my own answer. $\endgroup$
    – Buffy
    May 3, 2019 at 13:41
3
$\begingroup$

Bearing in mind that the only good analogy or example or metaphor of a computer thing is the computer thing, because computer things are not really like anything else (or they would just be that thing anyway):

I created an example for my students using a Tic Tac Toe game, which can be played by a human against the computer, or having it play many games against itself and tally the results. I created 5 strategies, each built on the one simpler than it, as follows

  1. Idiot - picks a square at random
  2. Naive - tries for center then corners, then invokes Idiot.
  3. Blocker - tries to block the predicted next move of opponent if it would lead to a win.
  4. Winner - tries for a winning move itself.
  5. Killer - (I forget how this one works, see if you can figure it out.)

I was able to explain how the strategies are based on a simpler one and add some capability. The 'Tournament' feature played each strategy against all others 10,000 times and showed the results. It was very convincing. So, games might be a good realm to explain.

$\endgroup$
4
  • $\begingroup$ I'll note that this is actually an instance of the "Strategy" design pattern. It, like other simple design patterns often makes use of polymorphism, but it is usually interface, rather than concrete, polymorphism. Objects of different classes can be interchanged (polymorphism) when they all have exactly the same public interface. Other sorts of ad-hoc subclassing is just a form of hacking. The OP is cautioned not to confuse subclassing with polymorphism. They are not equivalent. $\endgroup$
    – Buffy
    Apr 21, 2019 at 14:32
  • $\begingroup$ @Buffy I haven't knowingly used polymorphism in prior work, but perhaps I was doing the same thing according to what made sense to me at the time. Experience can lead people to conclusions, just as well as teaching can. But I have to wonder, if programming really is Engineering in some sense, then why do we expect masses of people to learn it? Few people dabble in helicopter piloting or brain surgery, and with so much riding on programming these days (everything, basically) and soon we will be riding IN programming, and self-learning programs at that, why try to interest everyone in doing it? $\endgroup$
    – Scott Rowe
    Apr 21, 2019 at 15:55
  • $\begingroup$ @ScottRowe It would be a sad world if people thought that the primary purpose of college was to provide students with job training. $\endgroup$ Apr 22, 2019 at 16:42
  • $\begingroup$ @SolomonSlow It is a sad world when they cannot get jobs after going to college, so both objectives must be met. What I have been reading on this site, is that many students cannot write a simple program after several years of instruction. That is even more sad. $\endgroup$
    – Scott Rowe
    Apr 22, 2019 at 22:08
3
$\begingroup$

As a professional software engineer, I used polymorphism all of the time. Here are a few examples.

  • Printer driver, is inherited by different printer drivers.
  • Window: different types: vertical packing, horizontal packing, stackable, free. Are all types of window, they all contain other displayables. Window also inherits from displayable (so windows can contain windows.
  • Container libraries.

I well designed system can use inheritance all over the place. But can also be over used.

Note there are many ways to do polymorphism, including:

  • run time, poylmorphism: virtual inheritance, templates, function pointers.
  • compile time: #include (arrange for different files to be included).
  • link time: arrange for different libraries to be linked.
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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