How To Create Extensible Software Components

As we grow as software engineers it is important for us to learn the best practices to improve the quality of the code. In this article, I would like to write on what is extensible software components and how we can implement them in our codebase by applying concepts such as abstraction, Dependency Injection.

Let’s assume that we have a list of Products and the product has the attributes such as id, category, and price as below,

public class Products {
    public int Id {
        get;
        set;
    }
    public string Category {
        get;
        set;
    }
    public double Price {
        get;
        set;
    }
}

Let us assume that we have several discount types which we have to apply for the price of products in the List based on the product category as follows.

If the category is clothing there should be a 10% discount should be applied.

There is a high chance that the types of discounts can be grown in the future for various product categories.

The need of writing extensible software arises here. Because in future there is a high chance of adding new discount types and in that case, we should be able to add that feature to the existing software program by having minimal impact to the existing software components.

We can apply the Open For Extension, Closed for Modification theory to write extensible software. This is also commonly known as the Open Closed principle (OCP) in SOLID principles.

Let us look at a software program that does not follow the Open closed principle (OCP) to write extensible software for the above scenario and later we will go through the code and refactor it and apply the Open closed principle to make the software easily extendable.           

public bool Write(List < Products > products) {
    try {
        foreach(var product in products) {
            if (product.Category == "Clothing") product.Price = product.Price - (product.Price * 0.1);
        }
        foreach(var product in products) {
            Console.WriteLine(product.Id + "| " + product.Price);
        }
        return true;
    } catch (Exception) {
        return false;
    }
}

When you look at the above code, it seems nothing is wrong and the calculation logic is correctly applied and everything works fine. But it is not. What if there is a new discount to be applied for the non clothing category or another specific category. The only way to do that is by adding another if condition to the above code and adding the new logic.

That is the place where you violate the principle of open-closed principle. A class should be closed for modification and if you are going to add another if condition, you are modifying the class and it is a violation of that principle.

So how we can avoid that?

*Note that here the WriteClass is created to add the functionality of printing the discounted price in Console.

As the first step, we should take the functionality of applying the logic of discounts out of the WriteClass. Because a class should not do so many things. At the same time, we also should be mindful to avoid the WriteClass should not depend on another concrete class called discount or whatever the class which has the discount calculation.

It is important to use the concept of Abstraction in Object oriented programming here. So that the WriteClass will not know how the discount is calculated and how many types of discounts are there and any changes we make in discount calculation will also be not known to the WriteClass. So the WriteClass’s duty of writing the records to a file will be isolated and it will not depend on other classes. This concept is called DECOUPLING of software components /classes.

In order to achieve the abstraction, we can use Interfaces in C#. I have created an interface named as IDiscountCalculator as follows and added a method for CalculateDiscount which returns a List of products.

public interface IDiscountCalculator {
    List < Products > CalculateDiscount(List < Products > activeProducts);
}

Next, I have created a concrete class called ClothingDiscount and implement the interface which I created earlier. And here the calculation logic for the category clothing is implemented.

public class ClothingDiscount: IDiscountCalculator {
    public List < Products > CalculateDiscount(List < Products > activeProducts) {
        foreach(var product in activeProducts) {
            if (product.Category == "Clothing") product.Price = product.Price - (product.Price * 0.1);
        }
        return activeProducts;
    }
}

As of now, we have taken out the responsibility of calculating the discount from WriteClass and put it in a separate class. Now let us see how we can use this class and the interface to calculate the discount.

For that, I am using the concept of dependency injection. Although it is a separate topic, For now, I will give a very basic idea on that.  

Dependency injection is a method to create decoupled software components. If there is a class that is dependent on another concrete class, we do not create an instance of that dependent class inside the class, whereas we inject it from outside in the runtime. That is a very basic idea of what dependency injection is and there are several ways to implement it.

Keeping this in mind, let us see the following implementation of the writing class.

public class WriteClass {
    private readonly IList < IDiscountCalculator > _discountTypes;
    public WriteClass(IList < IDiscountCalculator > discountTypes) {
        _discountTypes = discountTypes;
    }
    public bool Write(List < Products > products) {
        try {
            foreach(var item in _discountTypes) {
                products = item.CalculateDiscount(products);
            }
            foreach(var product in products) {
                Console.WriteLine(product.Id + "| " + product.Price);
            }
            return true;
        } catch (Exception) {
            return false;
        }
    }
}

Here as you can see, I have declared a private variable named discount types of type list for the interface IDiscountType. The reason for declaring it as a list is that in future we can have more than one discount type and in that case, it would be easy for us to handle it.

And there is a constructor with a parameter that takes a list of IDisccount types and assigns it to the private variable IDiscount type. Constructor is the place where we inject the concrete class (in this case a list of classes) from the outside.

Note interfaces is a very powerful option to plan the abstraction in our software program.

As the final step, I am iterating over all the discount types and invoking the calculate discount method. So that the relevant discount type calculation happens according to the logic which is specified in the respective discount class.

This way of implementation gives us a lot of benefits in terms of the maintainability of the software. The first thing I have to emphasize here is that the WriteClass does not depends on a Concrete class and it depends on an Interface. So basically whatever the calculation happens for the discount of the product, it does not matter for the WriteClass. And since we have decoupled the functionality of the discount calculation, even when there is a change in the calculation of discount, it does not affect the WriteClass. This way of writing code follows the Dependency Inversion Principle where it says that a Class should not depend on another concrete class and it should depend on an abstraction. We have to use the interfaces to plan the abstraction here.

Let us check the Main Method of this application to how to call the WriteClass to produce the output.

static void Main(string[] args) {
    var listOfDiscount = new List < IDiscountCalculator > ();
    listOfDiscount.Add(new ClothingDiscount());
    var write = new WriteClass(listOfDiscount);
    var product1 = new Products {
        Id = 1, Price = 100, Category = "Clothing"
    };
    var product2 = new Products {
        Id = 2, Price = 200, Category = "Clothing"
    };
    var product3 = new Products {
        Id = 3, Price = 300, Category = "NonClothing"
    };
    var product4 = new Products {
        Id = 4, Price = 400, Category = "NonClothing"
    };
    var listOfProducts = new List < Products > ();
    listOfProducts.Add(product1);
    listOfProducts.Add(product2);
    listOfProducts.Add(product3);
    listOfProducts.Add(product4);
    write.Write(listOfProducts);
}

As you can see in the main method I have created a variable to store the list of discount types which we have to apply for the products. Since we have only one discount type as of now I have added that discount type to the list. This is the place where we have to use the concrete class of clothing discount.

And then I have created an instance of the WriteClass by passing the list of the discount types which is required by the WriteClass. We can also say we are injecting the discount types to the WriteClass’s constructor.

Finally, I have added some products to the list of products variable with prices and invoke the write method by passing the list of products. Write method calculates the discount and gives the output after applying the discount. You can see the following output.

How to create Extensible Software Components

And let us assume there is another discount of 5% that should be applied for the categories which are not clothes. We can now simply implement the IDiscountClaculator interface again and create a new Class named General discount as follows. In this class, we can have the logic to calculate the discount for the products which is not clothing. 

public class GeneralDiscount: IDiscountCalculator {
    public List < Products > CalculateDiscount(List < Products > activeProducts) {
        foreach(var product in activeProducts) {
            if (product.Category != "Clothing") product.Price = product.Price - (product.Price * 0.05);
        }
        return activeProducts;
    }
}

Here we only have to add that discount type to the list of discount variables in the main method which stores all the discount types as follows. By doing that next time when we invoke the method "write" of WriteClass it will automatically calculate the discount for the new discount types as well. That is because we have designed the WriteClass in a way that we can adapt new changes to the program logic and as you can see I didn’t even touched the Write Class to add the new Change. This means that the WriteCLass is closed for modification but open for extension.

static void Main(string[] args) {
    var listOfDiscount = new List < IDiscountCalculator > ();
    listOfDiscount.Add(new ClothingDiscount());
    listOfDiscount.Add(new GeneralDiscount());
    var write = new WriteClass(listOfDiscount);
    var product1 = new Products {
        Id = 1, Price = 100, Category = "Clothing"
    };
    var product2 = new Products {
        Id = 2, Price = 200, Category = "Clothing"
    };
    var product3 = new Products {
        Id = 3, Price = 300, Category = "NonClothing"
    };
    var product4 = new Products {
        Id = 4, Price = 400, Category = "NonClothing"
    };
    var listOfProducts = new List < Products > ();
    listOfProducts.Add(product1);
    listOfProducts.Add(product2);
    listOfProducts.Add(product3);
    listOfProducts.Add(product4);
    write.Write(listOfProducts);
}

This is how you write easy to maintain software that has the capability of extensibility and maintainability. Also, the technique we have used to create decoupled software components is really important to create such maintainable software.


Similar Articles