The word polymorphism is derived from Greek, and means "having multiple forms":
Poly = many
Morph = forms
In programming, polymorphism is the ability of an object to take many forms.
To help you to understand polymorphism, we will first go through an example that doesn't use polymorphism. We will then discuss the issues with this solution, then refactor it to use polymorphism. This will give you a good understanding of what polymorphism is, and its ability to make software more flexible, extendable, testable, readable and elegant.
A bad example with no polymorphism:
public class Car
{
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public int NumberOfDoors { get; set; }
public void Start()
{
Console.WriteLine("Car is starting.");
}
public void Stop()
{
Console.WriteLine("Car is stopping.");
}
}
public class Motorcycle
{
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public void Start()
{
Console.WriteLine("Motorcycle is starting.");
}
public void Stop()
{
Console.WriteLine("Motorcycle is stopping.");
}
}
Let’s say that we want to create a list of vehicles, then loop through it and perform an inspection on each vehicle:
// Create a list of objects
List<Object> vehicles = new List<Object>
{
new Car { Brand = "Toyota", Model = "Camry", Year = 2020, NumberOfDoors = 4 },
new Motorcycle { Brand = "Harley-Davidson", Model = "Sportster", Year = 2021 }
};
// Perform a general inspection on each vehicle
foreach (var vehicle in vehicles)
{
if (vehicle is Car)
{
Car car = (Car)vehicle; // cast vehicle to a Car
Console.WriteLine($"Inspecting {car.Brand} {car.Model} ({car.GetType().Name})");
car.Start();
car.Stop();
}
else if (vehicle is Motorcycle)
{
Motorcycle motorcycle = (Motorcycle)vehicle; // cast vehicle to a Motorcycle
Console.WriteLine($"Inspecting {motorcycle.Brand} {motorcycle.Model} ({motorcycle.GetType().Name})");
motorcycle.Start();
motorcycle.Stop();
}
else
{
throw new Exception("Object is not a valid vehicle");
}
}
Notice the ugly code inside the foreach
loop! Because vehicles
is a list of any type of object (Object
), we have to figure out what type of object we are dealing with in each loop, then cast it to the appropriate object type before we can access any information on the object.
This code will continue to get uglier as we add more vehicle types. For example, if we extended our codebase to include a new Plane
class, then we’d need to modify existing code – we’d have to add another conditional check in the foreach
loop for planes, violating the Open/Closed SOLID Principle.
(By the way, if you're unfamiliar with the SOLID Principles and want to take your OOP skills to the next level, learning everything that you need to write elegant, maintainable, flexible and testable software -- then check out my book on Amazon Mastering Design Patterns in C#: A Beginner-Friendly Guide, Including OOP and SOLID Principles. It's also available on Gumroad.)
Now, back to our example...
Introducing: Polymorphism
Cars and motorcycles are both vehicles. They both share some common properties and methods. So, let’s create a parent class that contains these shared properties and methods:
public class Vehicle
{
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public virtual void Start()
{
Console.WriteLine("Vehicle is starting.");
}
public virtual void Stop()
{
Console.WriteLine("Vehicle is stopping.");
}
}
Car
and Motorcycle
can now inherit from Vehicle
:
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public override void Start()
{
Console.WriteLine("Car is starting.");
}
public override void Stop()
{
Console.WriteLine("Car is stopping.");
}
}
public class Motorcycle : Vehicle
{
public override void Start()
{
Console.WriteLine("Motorcycle is starting.");
}
public override void Stop()
{
Console.WriteLine("Motorcycle is stopping.");
}
}
Car
and Motorcycle
both extend Vehicle
, as they are vehicles. But what’s the point in Car
and Motorcycle
both extending Vehicle
if they are going to implement their own versions of the Start()
and Stop()
methods? Look at the code below:
// Program.cs
// Create a list of vehicles
List<Vehicle> vehicles = new List<Vehicle>
{
new Car { Brand = "Toyota", Model = "Camry", Year = 2020, NumberOfDoors = 4 },
new Motorcycle { Brand = "Harley-Davidson", Model = "Sportster", Year = 2021 }
};
// Perform a general inspection on each vehicle
foreach (var vehicle in vehicles)
{
Console.WriteLine($"Inspecting {vehicle.Brand} {vehicle.Model} ({vehicle.GetType().Name})");
vehicle.Start();
// Additional inspection steps...
vehicle.Stop();
Console.WriteLine();
}
In this example:
- We have a list,
vehicles
, containing instances of bothCar
andMotorcycle
. - We iterate through each vehicle in the list and perform a general inspection on each one.
- The inspection process involves starting the vehicle, checking its brand and model, and stopping it afterwards.
- Despite the vehicles being of different types, polymorphism allows us to treat them all as instances of the base
Vehicle
class. The specific implementations of theStart()
andStop()
methods for each vehicle type are invoked dynamically at runtime, based on the actual type of each vehicle.
Because the list can only contain objects that extend the Vehicle
class, we know that every object will share some common fields and methods. This means that we can safely call them, without having to worry about whether each specific vehicle has these fields or methods.
This demonstrates how polymorphism enables code to be written in a more generic and flexible manner, allowing for easy extension and maintenance as new types of vehicles are added to the system.
For example, if we wanted to add another vehicle, we don’t have to modify the code used to inspect vehicles (“the client code”); we can just extend our code base, without modifying existing code:
public class Plane : Vehicle
{
public int NumberOfDoors { get; set; }
public override void Start()
{
Console.WriteLine("Plane is starting.");
}
public override void Stop()
{
Console.WriteLine("Plane is stopping.");
}
}
// Program.cs
// Create a list of vehicles
List<Vehicle> vehicles = new List<Vehicle>
{
new Car { Brand = "Toyota", Model = "Camry", Year = 2020, NumberOfDoors = 4 },
new Motorcycle { Brand = "Harley-Davidson", Model = "Sportster", Year = 2021 },
/////////// ADD A PLANE TO THE LIST:
new Plane { Brand = "Boeing", Model = "747", Year = 2015 }
////////////////////////////////////
};
The code to perform the vehicle inspections doesn’t have to change to account for a plane. Everything still works, without having to modify our inspection logic.
Conclusion
Polymorphism is the term that explains different types of objects can be treated the same way, allowing us to flexibly pass around different types of objects in our code. Polymorphism makes software flexible, extensible, testable (e.g. we can pass a dummy database class to an object so that we don't have to test with a real database) and readable.
And if you'd like to learn all of the tools that you need to become a great object-oriented programmer, including:
- All 23 design patterns (“The Gang of Four Design Patterns”) with real world examples.
- OOP principles: encapsulation, abstraction, inheritance, polymorphism, coupling, composition, composition vs inheritance, fragile base class problem.
- The five SOLID principles: crucial for any developer that wants to work on OOP software and essential to know before diving into the design patterns.
- Unified Modelling Language (UML): the standard way to model classes and the relationships between them.
Then check out my book on Amazon Mastering Design Patterns in C#: A Beginner-Friendly Guide, Including OOP and SOLID Principles. It's also available on Gumroad.
Hope this article was helpful!
Thanks,
Danny
Top comments (0)