Polymorphism Part 2: Refactoring to Polymorphic Behavior


I spoke at the Houston C# User Group earlier this year.  Before my talk Peter Seale did an introductory presentation on refactoring.  He had sample code to calculate discounts on an order based on the number of items in the shopping cart.  There were several opportunities for refactoring in his sample.  He asked the audience how they thought the code sample could be improved.  He got several responses like making it easier to read and reducing duplicated code.  My response was a bit different; while the code worked just fine and did the job, it was very procedural in nature and did not take advantage of the object-oriented features available in the language.  

One of the most important, but overlooked refactoring strategies is converting logic branches to polymorphic behavior.  Reducing complicated branching can yield significant results in simplifying your code base, making it easier to test and read.

The Evils of the switch Statement

One of my first large applications that I had a substantial influence on the design of the application had some code that looked like this:

private string SetDefaultEditableText()
{
  StringBuilder editableText = new StringBuilder();
    switch ( SurveyManager.CurrentSurvey.TypeID )
    {
        case 1:
        editableText.Append("<p>Text for Survey Type 2 Goes Here</p>");                            
        case 2:
        editableText.Append("<p>Text for Survey Type 2 Goes Here</p>");
        case 3:
        default:
        editableText.Append("<p>Text for Survey Type 3 Goes Here</p>");
    }
    return editableText.ToString();
} 

Now there are a lot of problems with this code (a Singleton, really).  But I want to focus on the use of the switch statement. As a language feature, the switch statement can be very useful. But when designing a large-scale application it can be crippling and using it breaks a lot of OOD principles.  For starters if you use switch statement like this in your code, chances are you are going to need to do it again.  Now you’ve got duplicated logic scattered about your application.  If you ever need to add a new case branch to your switch statement you now have to go through the entire application code base and look where you used these statements and change them.

What is really happening is changing the behavior of our app based on some condition.  We can do the same thing using polymorphism and make our system less complex and easier to maintain.  Suppose you are running a Software as a Service application and you’ve got a couple of different premium services that you charge for.  One of them is a flat fee and the other service fee is calculated by the number of users on the account.  The procedural approach to this might be done by creating an enum for the service type and then use switch statement to branch the logic.

public enum ServiceTypeEnum
{
    ServiceA, ServiceB
} 

public class Account
{
    public int NumOfUsers{get;set;}
    public ServiceTypeEnum[] ServiceEnums { get; set; }
} 

// calculate the service fee
public double CalculateServiceFeeUsingEnum(Account acct)
{
    double totalFee = 0;
    foreach (var service in acct.ServiceEnums) { 

        switch (service)
        {
            case ServiceTypeEnum.ServiceA:
                totalFee += acct.NumOfUsers * 5;
                break;
            case ServiceTypeEnum.ServiceB:
                totalFee += 10;
                break;
        }
    }
    return totalFee;
} 

This has all of the same problems as the code above. As the application gets bigger, the chances of having similar branch statements are going to increase.  Also as you roll out more premium services you’ll have to continually modify this code, which violates the Open-Closed Principle.  There are other problems here too.  The function to calculate service fee should not need to know the actual amounts of each service.  That is information that needs to be encapsulated.

A slight aside: enums are a very limited data structure.  If you are not using an enum for what it really is, a labeled integer, you need a class to truly model the abstraction correctly.  You can use Jimmy’s awesome Enumeration class to use classes to also us them as labels.

Let’s refactor this to use polymorphic behavior.  What we need is abstraction that will allow us to contain the behavior necessary to calculate the fee for a service.

public interface ICalculateServiceFee
{
    double CalculateServiceFee(Account acct);
} 

Several people in my previous post asked me why I started with an interface and if by doing so is it really polymorphism.  My coding style is generally favors composition than inheritance (which I hope to discuss later), so I generally don’t have deep inheritance trees.  Going by the definition of I provided: “Polymorphism lets you have different behavior for sub types, while keeping a consistent contract.”  it really doesn’t matter if it starts with an interface or a base class as you get the same benefits. I would not introduce a base class until I really needed too.

Now we can create our concrete implementations of the interface and attach them the account.

public class Account{
    public int NumOfUsers{get;set;}
    public ICalculateServiceFee[] Services { get; set; }
} 

public class ServiceA : ICalculateServiceFee
{
    double feePerUser = 5; 

    public double CalculateServiceFee(Account acct)
    {
        return acct.NumOfUsers * feePerUser;
    }
} 

public class ServiceB : ICalculateServiceFee
{
    double serviceFee = 10;
    public double CalculateServiceFee(Account acct)
    {
        return serviceFee;
    }
} 

Now we can calculate the total service fee using these abstractions.

public double CalculateServiceFee(Account acct){
    double totalFee = 0;
    foreach (var svc in acct.Services)
    {
        totalFee += svc.CalculateServiceFee(acct);
    }
    return totalFee;
} 

Now we’ve completely abstracted the details of how to calculate service fees into simple, easy to understand classes that are also much easier test. Creating a new service type can be done without changing the code for calculating the total service fee.

public class ServiceC : ICalculateServiceFee
{
    double serviceFee = 15;
    public double CalculateServiceFee(Account acct)
    {
        return serviceFee;
    }
} 

But now we have introduced some duplicated code, since the new service behaves the same as ServiceB.  This is the point where a base class is useful.  We can pull up the duplicated code into base classes.

public abstract class PerUserServiceFee : ICalculateServiceFee
{
    private double feePerUser;
    public PerUserServiceFee(double feePerUser)
    {
        this.feePerUser = feePerUser;
    }
    public double CalculateServiceFee(Account acct){
        return  feePerUser * acct.NumOfUsers; 
   }
}
public abstract class MonthlyServiceFee : ICalculateServiceFee
{
    private  double serviceFee;
    public MonthlyServiceFee(double serviceFee)
    {
        this.serviceFee = serviceFee;
    } 

    public double CalculateServiceFee(Account acct)
    {
        return serviceFee;
    }
} 

Now our concrete classes just need to pass the serviceFee value to their respective base classes by using the base keyword as part of their constructor.

public class ServiceA : PerUserServiceFee
{
    public ServiceA() : base(5) { }
} 

public class ServiceB : MonthlyServiceFee 
{
    public ServiceB() : base(10) { }
} 

public class ServiceC : MonthlyServiceFee
{
    public ServiceC() : base(15) { }
} 

Also, because we started with the interface and our base classes implement it, none of the existing code needs to change because of this refactor.

Next time you catch yourself using a switch statement, or even an if-else statement, consider using the object-oriented features at your disposal first.  By creating abstractions for behavior, your application will be a lot easier to manage.

Polymorphism: Part 1