# Open-closed principle

Home Forums Programming Software engineering Object-oriented programming Open-closed principle

This topic contains 0 replies, has 1 voice, and was last updated by  Васильев Владимир Сергеевич 6 months, 2 weeks ago.

• Author
Posts
• #4093

This article provides a short and clear description for one of the main code design rules (SOLID) – an Open-Closed principle. It is demonstrating examples in C# programming language.

Open-closed principle is the easiest and the most obvious. It states: any program units (classes, structures, modules) should be open for extension and closed for modification.

If the class has already been written, approved, tested, possibly included to the library and then to the project, any attempt to modify it later is a bad idea. But other thing is the possibility to extend it through other available means. In fact, this principle simply involves the clever use of two principles of OOP: abstraction, and polymorphism.

Of course, the need to change the existing requirements to the code is not something rare, this is a normal situation. Another question is how the class itself can be prepared for its functionality to be extended in time. Therefore, data types have to be designed according to the possible need for extension in future.

Lets see an example of a class whose authors were too optimistic about its design, assuming that it is flexible enough to be ever modified.

This example describes a class ShopManager, which implements the possibility of selling products with the discount.

What catches the eye in this code – a limited list of goods, and it is unclear what to do if suddenly the store owners want to extend or change the range of those goods. From this perspective, the code is completely “wooden”, inflexible.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;

class ShopManager
{
public double Sell(string product, double price, double amount)
{
return Math.Round(MakeDiscount(product) * price * amount, 2);
}
public double MakeDiscount(string product)
{
switch (product)
{

case "apples": return 0.95; case "potatos": return 0.87;
case "strawberry": return 0.99;
default: return 0.0;
}
}
}
class Program
{
static void Main()
{
ShopManager shop = new ShopManager();
Console.WriteLine("The total i s {0} UAH", shop.Sell("apples", 11.5, 7.0));
}
}

The second thing that is puzzling – accrual of discounts. In this case, it just depends on what kind of product we buy. Neither the amount of the product or of the sales period, or from any other reasons. But in reality the principles of accrual discounts may depend on many factors. It may be promotions and wholesale prices, and discount cards and plenty of other options.

How to change the design of the ShopManager class to make it open for any extensions we might want to make in future?

First, it makes sense to immediately get rid of the hard binding to a particular list of products. Second, add some flexibility about different types of discounts (holiday, savings, promotions, etc.). Third, even if we distinguish reasons for discounts, you need to understand that for different types of products they can vary.

And here it becomes clear that it wont take just a couple of minutes to fix this code design. It requires significant improvements in three different directions: product types, types of discounts, different types of discounts for different goods.

And here it is the time to think about one of the OOP principles – abstraction.

We define the base interfaces for any type of products, type of discounts and rules for calculating the discount.

//interface for all kinds of products
interface IProduct
{
string Name { get; set; }
double Price { get; set; }
}

//interface for all types of discounts
interface IDiscount
{
double MakeDiscount(IProduct product, IDiscountRule rule);
}

//interface for all discount rules
interface IDiscountRule
{
double DiscountAmount { get; set; }
double SetRule(double coef);
}

Now we define specific classes that implement these algorithms:

//particular product class
class Apples : IProduct
{
public string Name { get; set; }
public double Price { get; set; }
}

//particular type of discount
class WholesaleDiscount : IDiscount
{
public double MakeDiscount(IProduct product, IDiscountRule rule)
{
//
return product.Price * rule.DiscountAmount;
}
}

class ApplesDiscountRule : IDiscountRule
{
public double DiscountAmount { get; set; }
public double SetRule(double coef)
{
//defining the algorithm of wholesale discountt for particular product
//...
DiscountAmount = coef;
return coef;
}
}

Now we change ShopManager class so that you could make sales with and without discounts:

class ShopManager
{
public double Sell(IProduct product, double amount)
{
//selling the product without discount
return  Math.Round(product.Price * amount, 2);
}
public double SellWithDiscount(IProduct product, IDiscount discount, IDiscountRule rule, double amount)
{
//selling the product with discount
return  Math.Round(MakeDiscount(product, discount, rule) * amount, 2);
}
public double MakeDiscount(IProduct product, IDiscount discount, IDiscountRule rule)
{
//implementing the discount algorithm
return double rs = discount.MakeDiscount(product, rule);
}
}

And now let’s test the program again after making changes:

class Program
{
static void Main()
{
var shop = new ShopManager();
var apples = new Apples() { Name = "Apples", Price = 11.5 };
Console.WriteLine("The total without discount is {0} UAH", shop.Sell(apples, 7.0));
var rule = new ApplesDiscountRule();
rule.SetRule(0.95);

Console.WriteLine("The total with discount is {0} UAH",
shop.SellWithDiscount(apples, new WholesaleDiscount(), rule, 7.0));
}