Polymorphism or how to decouple structure from behavior

The 2 sides of the coin

There are 2 aspects to any software piece: a static and a dynamic one, the former being the code and the latter being the execution of that code. The static part deals with the structure of the software while the dynamic part deals with the behavior of the software. This duality in the software is often ignored by a lot of developers, yet is there and its effects are tangible.

The structure and behavior relationship

There is a strong relationship between structure and behavior. The behavior is conditioned to the structure represented in code. You can have a rigid structure, that is, a structure that don’t allow the change of behavior without any changes to itself. The opposite is a flexible structure that allows changes to behavior without changing the structure itself.

Structure vs Code

It’s important to understand that code is not the same as structure. You can change the code without changing the structure of the software. Renaming a variable, even changing a variable, are changes that do not affect the structure of the software. The structure is more of a conceptual model, coupled with some conceptual mechanisms that is implemented by a programming language in the form of code. The way the conceptual model is defined will be affected by the programming paradigm used by the developer. You’ll come to different models/structures using different paradigms. Also the conceptual mechanisms will differ from one paradigm to another and in some cases from one language to another.

The case against switch

Put simply the purpose of polymorphism is to create a flexible structure that allows the software to change it’s behavior without changing the structure. There may be changes to the code, but not to the structure.

Consider the following code:

public enum EmployeeType
{
    Manager, Worker
}

public struct Employee
{
   public string ID {get; set;}
   public string FirstName {get; set;}
   public EmployeeType Type {get; set;}
}

public void MakePayment(Employee employee)
{
    decimal wage = 0;
    switch(employee.Type)
    {
      case EmployeeType.Manager:
        wage = 30m;
      break;
      case EmployeeType.Worker:
        wage = 10m;
      break;
    }
   decimal payment = wage * getWorkedHours(employee.ID);
...
}

Suppose we want to add a new employee type, Janitor. To make the software make payments to the Janitor (change in behavior) we would need to add a Janitor value to the EmployeeType enum and modify the switch to accommodate the new value. This is a rigid structure: you need to tweak it to make it learn new tricks. This is a typical procedural style (using C#).

The worst part of the code above is that by changing the Employee type from struct to class and putting the MakePayment procedure inside a class (most of the time as an static method) an lot of developers believe that they are now doing OOP.

Let’s see how this would look like from an OOP paradigm.

public interface Role
{
   decimal GetWage();
}

public class Manager: Role
{
   public decimal GetWage() { return 30m;}
}

public class Worker: Role
{
   public decimal GetWage() { return 30m;}
}

public class Employee
{
   Role _role;

   public Employee(Role role){
      _role = role;
   }

   public string ID {get;}
   public decimal Wage{ get{ return _role.GetWage(); }}
}

public class Timesheet
{
   public bool Pay(Employee employee)
   {
      decimal payment = employee.Wage * getWorkedHours(employee.ID);
      ...
   }
}

So in this code, if you wanted to add a Janitor employee, all you have to do is to create a new Role class that represent the Janitor role. And that’s it. That’s a code change, not a structural one. The price for this flexibility is indirection. Now there are a lot of classes, each one representing a case in the switch. That’s how we deal with branching in OOP. And that’s why OO software tends to be way more flexible than procedural software.

Final words

I hope this helps making the point clear. I tried keeping it simple, so maybe the example may look silly. I would like to say that polymorphism is not a characteristic of OOP alone: C allowed to define some sort of interface for a function and the languages in the Functional programming paradigm make heavily use of it too. Even when the form may vary, the idea it’s almost always the same: decouple dependencies and allow the creation of a flexible software structure, making it resilient in the face of change.