OCP Open-Closed Principle

August 21st, 2006 Comments

Another fundemental principle of object-oriented software design is the Open-Closed Principle. According to Uncle Bob (Robert Martin), this principle states that:

Software entities (classes, modules, functions, etc.) should be
open for extension, but closed for modification.

Essentially, what this means is that software’s design should be such that it’s behavior can be modified by extending the existing source code rather than modifying it. Consider the following example:

public class CheckingAccount
{
    private double _currentBalance;

    public void ProcessDeposit(double amount)
    {
        _currentBalance += amount;
    }
}

public class MortgageAccount
{
    private double _currentBalance;

    public void ProcessPayment(double amount)
    {
        _currentBalance -= amount;
    }
}

public class Bank
{
    public void ReceiveMoney(object account, double amount)
    {
        if (account.GetType() == typeof(CheckingAccount))
        {
            CheckingAccount checkingAccount = (CheckingAccount)account;
            checkingAccount.ProcessDeposit(amount);
        }
        else if (account.GetType() == typeof(MortgageAccount))
        {
            MortgageAccount mortgageAccount = (MortgageAccount)account;
            mortgageAccount.ProcessPayment(amount);
        }
    }
}

Notice that the ReceiveMoney() method of the Bank class has to determine the type of account passed in. Only based on that information it can perform the appropriate business logic. Later if we add another account type then we may need to modify this code in order for it to work properly. The above code is, hence, in violation of the Open-Closed principle.

So how can we revise our code to conform to the Open-Closed Principle? Abstraction is the answer. Let’s take a look:

public abstract class Account
{
    public abstract void Deposit(double amount);
}

public class CheckingAccount : Account
{
    private double _currentBalance;

    public override void Deposit(double amount)
    {
        _currentBalance += amount;
    }
}

public class MortgageAccount : Account
{
    private double _currentBalance;

    public override void Deposit(double amount)
    {
        _currentBalance -= amount;
    }
}

public class Bank
{
    public void ReceiveMoney(Account account, double amount)
    {
        account.Deposit(amount);
    }
}

Notice how we got rid of all conditional logic in the ReceiveMoney() method. Also notice that if we later add a new account type, the ReceiveMoney() method will work without any modifications. The revised version of our code above, hence, conforms to the Open-Closed Principle.

Let’s now consider what happens to our code if a new business rule is added that requires an e-mail notification to be sent to the account holder whenever a deposit is made. Obviously, we’ll have to modify the ReceiveMoney() method to perform that logic. Since we can’t account for all possible scenarios, we can never acheive perfect closure. With that in mind, design the simplest software that will do the job, and conform to OCP or other such design principles where you see the need.

SRP: Single Responsibility Principle

August 20th, 2006 2 comments

SRP is one of the fundamental object oriented software design principles, first introduced by Tom DeMarco in 1979.

In simple terms, SRP states that every class should have a single well defined goal, and that all members of that class (properties, methods, etc.) should work together to achieve that goal. Robert Martin puts it in slightly different terms:

There should never be more than one reason for a class to change.

Essentially SRP enforces the concept of high cohesion that should not only be applied to classes but also internals of a class, such as methods.

So why is SRP such an important design principle? It is because a class with multiple responsibilities is more difficult to change and more difficult to reuse (design smells: fragility and immobility).

While SRP is quite easy to understand, it’s not always logical to implement. Consider the following class:

Given the convenience of having a customer class that is smart enough to know how to load and save itself, would you rather break it out like this?:

According to SRP, you should, because the combined customer class shares multiple responsibilities: customer business logic, and customer persistence logic. In fact if you use TDD, you may even be forced to separate these two responsibilities.

In practicality, however, while we should keep this principle in mind and try to apply it wherever it makes sense, we should never get so carried away that we start introducing other design smells in our architecture, such as Rigidity, Viscosity, and Needless Complexity.