1

I have read many tutorials on Polymorphism and all of them show examples of Polymorphism used in arrays, for example you would have an Animal (parent class) array, and its elements are Cat and Dog and Rabbit (child classes), and then you would loop through the array and call Animal.speak() on each element.

Are there others ways in which Polymorphism is used (other than in arrays)?

4
  • 2
    You may want to have a look at design patterns. Almost all of them make use of polymorphism (beyond arrays)-. Commented May 11, 2020 at 18:35
  • 1
    You yourself posted an example from a GUI framework in your last question. Nothing to do with arrays. Commented May 11, 2020 at 18:53
  • Also, the answers to your last question contain a number of additional examples. Commented May 11, 2020 at 19:11
  • 1
    Polymorphism is a completely orthogonal construct to arrays; indeed, polymorphism works in exactly the same way regardless of whether the object happens to be referenced by an array element, or an element in some other kind of data structure, or even just any plain reference variable. Commented May 11, 2020 at 19:28

6 Answers 6

11

The point of polymorphism isn't arrays. It's that you can send an object a message without knowing exactly what kind of object you're talking to.

out.print("Hello world");

This might seem like it will print "Hello world" to a console, but we don't know that. We don't know what out is. This might display "Hello world" in a popup dialog box. It might have the computer say, "Hello world" through an audio speaker. It might send a text message. We don't know because we're looking at code that doesn't have to know. That's polymorphism. What it does depends on what out really is and how it was configured. No arrays required.

The benefit is the ability to isolate knowledge. Code that doesn't know the console exists doesn't need to change when it suddenly doesn't. It makes rewriting code easier because the impact of a change is small. The homogeneous array trick is often a crude attempt to show off that ability. Doesn't help if the result is that people think you need the array for this to work.

1
  • This practical example is a hundred times better than any Cat, Dog and Animal example will ever be.
    – Joe
    Commented May 13, 2020 at 18:15
4

It's for the ease of explanation:

  • Arrays, lists, and other forms of collections/containers make it easy to quickly demonstrate the usefulness of having a same function called repetitively on different elements with different behaviors.

  • Btw, most real life applications don't deal with Animals and Shapes either, despite these are frequently used in polymorphism tutorials.

While in real life, polymorphism is often used with containers, it's as often used independently of containers:

  • on isolated objects, at compile-time or at run-time. But if you'd be new to polymorphism and encounter such a case in a tutorial , you'd immediately question why to create an Animal object if you know it's a Dog.
    A typical use is for decoupling different kind of objects and allow evolution through specialization/subclassing (in some languages interfaces would be preferred to classes for this purpose). Another example is the technique of dependency injection which relies on polymorphism.
  • for objects stored in more complex structures than flat containers (e.g. trees, graphs, ...). But if you'd be new to OOP, you'd be lost when trying to understand the context.
  • for making the development evolutive using for example OCP in conjunction with polymorphism and subtyping.
  • for making classes more reusable based on abstraction and taking advantage of for example LSP (caution: polylorphism is not necessary for LSP, but LSP makes reusability of polymorphic classes more effective and less error-prone)

If you want to get some more detailed examples of the practical use of polymorphism without containers, you should have a look at some common design patterns: many of them leverage polymorphism for the sake of a better and more flxeible design. But finish your tutorial first ;-)

1

Polymorphism means having multiple forms under a single formal definition. Your formal definition focuses on a role and any form might belong to this definition as long as it fulfills the role.

Object-oriented programming is all about roles. Polymorphism refers to the assurance that, as long as you speak with roles, your job can be done by (potentially) multiple, otherwise distinctly different, objects.

The benefit of polymorphism is that it shifts the focus from details to roles. Think about it, you walk out of the door, go straight to your car, which refuses to start. Luckily, you have the following notes in your notebook:

  • Jack, a religious lawyer, who lives in Arizona, has two children and a wife. He has two brothers, which, along with his father, own a car repairing firm, he used to play at the garage when he was young. Having spent most of his childhood there, he is actually an experienced car mechanic.

  • Lucy is a passionate environmentalist, a truly spiritual woman that is into fashion and enjoys the snow and everything white in general. She is a light smoker. She has been a certified car mechanic for the last 10 years, she will be 35 in a month or so.

  • Jason is a computer engineer and a pianist by profession. He has a weird attitude towards life, he enjoys loneliness and seems to love beer more than any being on this world. He is not good friends with sun, but is forced to go shopping every now and then. The son of a racer, he has grown around cars and knows everything around engines and all.

When your car needs fixing, the only details you really need to know about Jack, Lucy and Jason is that they fix cars. They enact this role. So they can be grouped under that respect and this is why all examples around end up adding interfaces into arrays... because it is an easy way to illustrate this grouping. The point is in the conceptual grouping, not necessarily the material grouping, but the arrays help in stressing this aspect, that's all.

IFixCars carFixer = new Jack();
IFixCars carFixer2 = new Lucy();
IFixCars carFixer3 = new Jason();
...

In short, polymorphism is, in a way, the power to express your needs by virtue of roles, rather than details. You don't need Tom the lawyer, you just need a lawyer.

0

I have read many tutorials on Polymorphism and all of them show examples of Polymorphism used in arrays

The arrays themselves are not really the point, the key thing is that polymorphism lets you treat different objects in the same way. An array just provides an easy/obvious example of that; e.g., if you loop over the elements, the code in the loop body is the same for every object, and that's possible because that code has no idea what the actual type of the object is. The interface1 of Animal allows you to write it in such a way so that it doesn't have to know. All it requires is that all of the objects support speak().

I think what you're really asking is in what circumstances (other than arrays) such a situation arises. You'll find more opportunities to use polymorphism as you get more experienced.

A motivating example

One scenario that's common enough is when you have to support two pieces of functionality that both have the same overall logic, but differ in certain details. E.g., you might want to process some data, and have the ability to save the result in a couple of different file formats - say JSON and XML. You could, in principle, write the code twice, but you'll end up having a lot of repetition, and if you have to change something, you'll have to remember to do it in two places. You could also clean up the code a bit, and separate the part that saves the result into an if statement that saves in one or the other format. Maybe your method would accept some kind of a flag or an enumeration, and the if statement then decides what to do based on that:

ProcessDataAndExport(ExportFormats.JSON)

But, if you need to support another format, you'll need to edit the code again. And then again if you need another one. And again, and again.

Instead, you could separate out the saving logic to a different class. To use polymorphism, you come up with an abstraction - a base class, say Exporter, that provides the abstract interface1 that ProcessDataAndExport will use. BTW, "come up with an abstraction" just means that you define some type that lets you write the code of ProcessDataAndExport in a way that doesn't require you to know about the concrete details of the export process (it's not unusual to not get this right on the first try - you may need to refine the Exporter class as you add support for new formats, but after 3 or 4 of them, it should become fairly stable).

class Exporter {
    public abstract Export(MyDataType myData, string outputPath);
}

And then you can change ProcessDataAndExport to accept an exporter instead:

public ProcessDataAndExport(Exporter exporter) {
    // process data (omitted)...

    // no if anymore; the choice is not made in this method
    exporter.Export(data, outputPath);   
}

Then you can call it like this:

Exporter jsonExporter = new JsonExporter();
ProcessDataAndExport(jsonExporter);

Or like this:

Exporter xmlExporter = new XmlExporter();
ProcessDataAndExport(xmlExporter);

Or like this:

Exporter userSelectedExporer = ShowSelectExportFormatDialog();
ProcessDataAndExport(userSelectedExporer);

To support new formats, all you need to do is define another class deriving from exporter.

But wait, there's more! Here's something interesting; the concrete exporters don't even have to be defined in the same library (say, same EXE, DLL, or JAR, or whatever) as your ProcessDataAndExport function. They could come from a plugin (or a number of plugins) that the application has loaded. Because ProcessDataAndExport doesn't hardcode the formats, it can work with any of them.

TheShowSelectExportFormatDialog has a way of discovering all the exporters that come from plugins and creating instances of them, but the code in ProcessDataAndExport doesn't have to worry about that - it can treat all the exporters in the same way, just like the loop body in the array example.

With this, you can add support for new kinds of exporters without having to recompile/redeploy the library that has ProcessDataAndExport in it (e.g., your users can just download the new plugins).

Discussion

Now, this whole thing is partially contrived, as in some languages, instead of an exporter, you may use something called a Serializer. It also essentially converts your data/objects into some output format, it just works a little bit differently (e.g., it might use reflection to discover properties defined on your object, and write them out automatically).

But the thing is, in much the same way as described above, you can write your own serializer, for your own custom type, by creating a derived class. And that's possible even though the base type from which you're deriving is defined in a separate library that comes with the language. Furthermore, say you are using some 3rd-party framework that provides some operation that accepts a serializer. You can easily pass your own custom serializer to it, and it will polymorphically call it. The developers of that framework couldn't have predicted what kinds of formats you'd want; in fact, they'll probably never know about your class. Even so, their code works with your code - because it's written in a way that lets it treat all serializers, known and unknown, in the same way, again, just like the loop body in the array example.

Here's an example you might be familiar with; you may have used a GUI framework before. Often, you derive your own MyWindow or MyForm from a framework-defined base class (Window, Form). That framework has no idea about the details of your window class. But, if you override an inherited method, the framework will just call it polymorphically, and that will cause your version to run.

Another common use for polymorphism is to isolate objects for testing; e.g., when you write a test for some class, and you want to focus only on a certain aspect of it, you might want to pass in a fake object (that either does nothing or something test-specific) instead of the real one you'd use in production. Or, if the class makes a call to something that's inconvenient in the testing scenario (e.g., to a network service or a database), you can use polymorphism to prevent that, and maybe return some fake data.

There are all kinds of examples like this. Library functions that accept some interface type use polymorphism. Library functions that accept lambdas also use polymorphism in much the same way (their code doesn't know the details of the concrete function it accepts). As another answer mentioned, many design patterns rely on a combination of polymorphism and composition to achieve some design goal.

P.S. Also, if one day it turns out that you need to support different data processing algorithms, you may end up with something like this instead:

ProcessDataAndExport(dataProcessor, exporter);

Now ProcessDataAndExport just makes polymorphic calls to the two objects and coordinates the overall flow. Concrete DataProcessor is concerned with the specific processing algorithm, and it returns some result, that ProcessDataAndExport then passes on to a concrete Exporter, that's in turn responsible for producing the output.


1 By "interface" I just mean the set of public methods/properties on the base type (this is the general meaning of the word); I don't mean specifically the thing declared by interface keyword in languages like C# and Java (although that defines an interface in this sense as well).

0

Polymorphism isn't explicitly connected with arrays, and the examples you give look like classes. Polymorphism is one of the four principles of Object Oriented Programming (the other three being (encapsulation, abstraction, inheritance), and is primarily connected with the last one, inheritance.

If you'd like to know the basics of OOP, there's no better video series (that I'm aware of) than the one hosted by Simon Allardice.

This is a good start: Computer programming: What is object-oriented language?

2
  • 1
    Eh, not a fan of that explanation. Object Oriented Programming is totally a misnomer. It's not about the objects (dumb structs have been around for ages, and it's trivial to add functions that operate on them). It's about message passing, and polymorphism.
    – Alexander
    Commented May 11, 2020 at 20:14
  • @Alexander-ReinstateMonica Riiiight Commented May 11, 2020 at 23:29
0

The Short answer I think, is that polymorphism enables concepts that are incredibly powerful, let's consider them in light of a sample project: imagine you are maintaining a messaging library, the intention is that you have 'SenderServices' such as EmailService, SmsService etc. Each of these their implement their own send(recipient, message)

Now, the developer implementing the client is probably not very excited about the thought of implementing a client class for each of these. Therefore, you have made use of 'Subtype polymorphism' by creating the interface ISenderService, which has an abstract method with the following header: send (recipient, message) and then you let both of your services be implementations of this interface.

This way, the client does not need to know about EmailSender nor SmsSender but it does get passed a ISenderService which is what it uses. This way, the client can send messages, and it will go via SMS or email as per the intention of the one who passed it the instance.

Let's have a look at the consequences of this approach:

  1. If the implementation of either service ever changes, the client does not care, it only cares that the interface stays the same
  2. If a new implementation is ever added, could be DiscordSender the client can just have that passed, and everything will still function the same

Which brings me to something else this touches on...

Coding by addition, not modification what this is, is essentially the idea, that when you need to make a change (not just a bug fix) you do so, not by modifying existing code, but by adding interface, and implementing them, perhaps also by inheriting your existing implementations. An example:

Turns out you need to also be able to send pictures. Now, this needs you to create a new interface: IPictureSenderService now, this sender has a method like: send_picture(recipient, text, image) but you would like it, to also be able to handle just the text bit so the implementations can also be relief upon by clients using ISenderService. Well, we create MmsService however, this class inherits SmsService which in turn means it has its functionality and now is both a IPictureSenderService and a ISenderService and can be used by clients relying on either.

There are lots of other benefits, but you will discover a lot of them as you work with object oriented languages

Parametric Polymorphism

This is a bit different, but I'm essence, it's when classes are 'generic'. This is when you code a class that takes a type argument such as the List in C#

What this does is, in essence that you decide what types are used by the library class, in your client class. So there's no casting.

I recommend you read about it, but in essence it is used in situations where you want objects in a class to be of a specific type, e.g. a list of fish

Your Answer

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

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