Open-Closed Principle

 

OCP

 

Definition

According to Bertrand Meyer:

A class should be open for extension, but closed for modification

According to Wikipedia:

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

Open for Extension:

»    New behavior can be added in the future

»    Which means that

o    If new requirements arise or requirements of an application changes, it will be able to add new features to meet the requirement without making any change in existing source or without affecting dependent modules.

o    If we use the code in another application then it will be able to easily meet the requirements of that application by adding required behavior of that application.

 Closed for Modification:

»    Changes to existing source or binary code are not required i.e.  no modification required in existing source during

o    Add new features with existing source

o    Add existing source in another application.

Benefits:

»    Application will be more robust because we are not changing already tested class.

»    Flexible because we can easily accommodate new requirements.

»    Enhance reusability of classes, so we can easily avoid duplication of programming.

»    Avoid rigidity and fragility of code.

»    Though OCP prevent modification of existing source, so no chance introducing new bugs in existing code

»    Also prevents fixing bugs from existing code (if existing code is already bug free)

»    Efficient unit testing facility.

Goals:

»    Adding new behavior to existing class without introducing bugs and errors in existing source.

»    Prevent the modification of existing source

How to achieve the goal:

»    Identify the frequent changeable areas of the domain and use abstraction i.e. abstract classes, interfaces on those areas.

»    Then add new features by creating new extensions i.e. implementations of these abstractions

Some ways to keep your code closer to confirming OCP:

»    Making all member variables private so that the other parts of the code access them via the methods (getters) and not directly.

»    Avoiding typecasts at runtime because this makes the code fragile and dependent on the classes under consideration, which means any new class might require editing the method to accommodate the cast for the new class.

Example:

»    First we will create a price calculation system of e-commerce project without using open closed principle

»    Then we will refactor our code to achieve open closed principle.

»    Let’s create a class with following definition

  
    public class ShoppingCart
    {
        private readonly List _orderItems;

        public ShoppingCart()
        {
            _orderItems = new List();
        }

        public IEnumerable OrderItems
        {
            get
            {
                return _orderItems;
            }
        }

        public string CustomerEmail { get; set; }

        public void Add(OrderItem orderItem)
        {
            _orderItems.Add(orderItem);
        }

        public decimal TotalAmount()
        {
            decimal total = 0m;
            foreach (OrderItem orderItem in OrderItems)
            {
                if (orderItem.Identifier.StartsWith("Each"))
                {
                    total += orderItem.Quantity * 4m;
                }
                else if (orderItem.Identifier.StartsWith("Weight"))
                {
                    // 1 kg = 1000 gm
                    total += orderItem.Quantity * 3m / 1000; 
                }
                else if (orderItem.Identifier.StartsWith("Spec"))
                {
                    total += orderItem.Quantity * .3m;
                    int setsOfFour = orderItem.Quantity / 4;
                    //discount on groups of 4 items
                    total -= setsOfFour * .15m;
                }
            }
            return total;
        }
    }

»    Here TotalAmount() function calculate the total price of cart.

»    Here we used three strategies for calculate the price

  • Price per piece
  • Price per unit of weight e.g price per kg
  • Special discount based on purchase i.e. buy 3 get 1 free

»    These types of pricing rules can be changes frequently

»    Also so many pricing strategy and discount strategy can be added.

»    In that case programmer have to extend the if… else statement based one each changes or each addition of strategy

»    Also have to modify existing calculation of price if strategy changes.

»    Sometimes need to introduce nested if… else condition for more complex calculation.

»    If we follow this process and adding of strategy increasing frequently then once the code will get out of hand i.e. this method will be loss its maintainability

»    Also there will be a lot of possibility to introduce bugs and errors so that we will need to test this method each time after modification of code.

»    Pricing logic also tightly coupled with shopping cart so that it will be difficult to unit test because we have to test all other objects that are dependent on ShoppingCart even the don’t have any relation with pricing rules.

»    The bug fixing and maintaining of the code become more and more complex for programmer

Refactor the code

»    We will use open closed principle so that we don’t need to modify particular code based on changing or adding of pricing strategies.

»    In this case we will separate the pricing logic into different classes and hide them behind the abstraction that can be referred by ShoppingCart.

»    Resulting is a lot of classes but all of them are small in size and concrete on some very specific functionality

»    Though there is no class dependent on it and there is no coupling, so these classes can be test separately.

»    First of all for different pricing rule / pricing strategy we will create following interface

    public interface IPriceStrategy
    {
        bool IsMatch(OrderItem item);
        decimal CalculatePrice(OrderItem item);
    }

»    The IsMatch() method will determine which concrete strategy have to call OrderItem.

»    Now we will define different concrete price calculation strategy classes by inherit IPriceStrategy.

»    Inside these classes we will encapsulate the strategy selection and price calculation logic.

»    So first of all we will create PricePerUnitStrategy class with following definition:

    public class PricePerUnitStrategy:IPriceStrategy
    {
        public bool IsMatch(OrderItem orderItem)
        {
            return orderItem.Identifier.StartsWith("Each");
        }

        public decimal CalculatePrice(OrderItem orderItem)
        {
            return orderItem.Quantity * 4m;
        }
    }

»    Next we will create another Strategy class that will implement the logic for calculate the price per kilogram with following definition.

    public class PricePerKilogramStrategy:IPriceStrategy
    {
        public bool IsMatch(OrderItem orderItem)
        {
            return orderItem.Identifier.StartsWith("Weight");
        }
        public decimal CalculatePrice(OrderItem orderItem)
        {
            return orderItem.Quantity * 3m / 1000;
        }
    }

»    Next we will create another strategy class for special discount. If customer purchases 4 items then he/she will get 15% discount.

»    The code definition of this strategy class is given below:

    public class SpecialPriceStrategy : IPriceStrategy
    {
        public bool IsMatch(OrderItem orderItem)
        {
            return orderItem.Identifier.StartsWith("Spec");
        }

        public decimal CalculatePrice(OrderItem orderItem)
        {
            decimal total = 0m;
            total += orderItem.Quantity * .3m;
            int setsOfFour = orderItem.Quantity / 4;
            total -= setsOfFour * .15m;
            return total;
        }
    }

»    Then we will create a Calculator for calculate the correct price.

»    First of all we will define an Interface for Calculator with following definition.

    public interface IPriceCalculator
    {
        decimal CalculatePrice(OrderItem orderItem);
    }

»    Then we will create concrete Price Calculator class by inheriting IPriceCalculator interface

»    The CalculatePrice() method of this class will use to select the correct strategy class and calculate the price for that strategy.

»    The definition of that class is as follows:

    public class PriceCalculator:IPriceCalculator
    {
        private readonly List _pricingRules;

        public PriceCalculator()
        {
            _pricingRules = new List();
            _pricingRules.Add(new PricePerUnitStrategy());
            _pricingRules.Add(new PricePerKilogramStrategy());
            _pricingRules.Add(new SpecialPriceStrategy());
            _pricingRules.Add(new BuyThreeGetOneFree());
        }

        public decimal CalculatePrice(OrderItem orderItem)
        {
            return _pricingRules.First(p => p.IsMatch(orderItem)).CalculatePrice(orderItem);
        }
    }

»    We store all possible strategies in the constructor.

»    Then inside the CalculatePrice method

  • We check exact OrderItem using IsMatch() method.
  • Then call the CalculatePrice() method of that OrderItem i.e. matching OrderItem.
  • We complete entire process by using LINQ

»    Now we will simplify our ShoppingCart class as follows:

    public class ShoppingCart
    {
        private readonly List _orderItems;
        private readonly IPriceCalculator _priceCalculator;

        public ShoppingCart(IPriceCalculator priceCalculator)
        {
            _priceCalculator = priceCalculator;
            _orderItems = new List();
        }

        public IEnumerable OrderItems
        {
            get 
            { 
                return _orderItems; 
            }
        }

        public string CustomerEmail { get; set; }

        public void Add(OrderItem orderItem)
        {
            _orderItems.Add(orderItem);
        }

        public decimal TotalAmount()
        {
            decimal totalAmount = 0m;
            foreach (OrderItem orderItem in OrderItems)
            {
                totalAmount += _priceCalculator.CalculatePrice(orderItem);
            }
            return totalAmount;
        }
    }

»    Here we injected IPriceCalculator through the constructor of ShoppingCart.

»    We will pass concrete implementation of IPriceCalculator i.e. PriceCalculator class to calculate the price base on the ordered items through Ioc.

»    So ShoppingCart is not responsible for calculate the actual price.

»    Now if the business owner wants to add new pricing rule then we will add another strategy class as follows:

    public class BuyThreeGetOneFree:IPriceStrategy
    {
        public bool IsMatch(OrderItem orderItem)
        {
            return orderItem.Identifier.StartsWith("Buy3OneFree");
        }

        public decimal CalculatePrice(OrderItem orderItem)
        {
            decimal totalPrice = 0m;
            totalPrice += orderItem.Quantity * 1m;
            int setsOfThree = orderItem.Quantity / 3;
            totalPrice -= setsOfThree * 1m;
            return totalPrice;
        }
    }

»    Then we will add this class in the List of PriceCalculator as follows:

_pricingRules.Add(new BuyThreeGetOneFree());

»    Now it will be found by LINQ statement.

»    Here we see that we don’t need to change existing code for add new price calculation rule.

»    We achieved it by using Open-Closed principle.

Md. Mojammel Haque
Currently working as Lead Team (Application Architecture) at Raven Systems Ltd.

Passion for software development especially agile practices such as TDD & BDD with in depth knowledge of OOP, DDD, CQRS, ES, GoF, SOLID and PoEAA.

Over 6 years of software development experience ASP.NET. Has the ability to understand and transform complex business requirements into software ensuring applications are delivered on time.

Also experience in non Microsoft .NET technologies such as Dapper.Net, Subversion, Structure Map & AngularJs.

1785 Total Views 3 Views Today
Single Responsibility Principle
Project Euler Solution - 01

Comments (12)

  1. Hi there, just became aware of your blog through Google, and found
    that it is really informative. I’m gonna watch out for brussels.
    I will appreciate if you continue this in future. Numerous people will be benefited from your writing.
    Cheers!

  2. Hi! This post couldn’t be written any better! Reading through
    this post reminds me of my good old room mate!
    He always kept talking about this. I will forward this article
    to him. Pretty sure he will have a good read.
    Many thanks for sharing!

  3. Generally I don’t learn article on blogs, but I would like
    to say that this write-up very forced me to try and do so!

    Your writing style has been surprised me. Thanks, very nice post.

Leave a Comment

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>