Solid principles: 2. Open-closed principle

The second SOLID principle, open-closed, ie open to extension, closed to modifications. What is going on? About this further.

This rule says that the class and its modules must remain open for expansion, i.e. you can add new methods and functionalities, but with this expansion you can not modify anything in existing modules, change their activities because it would be breaking this rule.

Modification of the class is strictly prohibited, because the change in the operation of existing classes may affect the operation of others, which will disturb the operation of the whole system and will disintegrate. A good solution does not create further problems.

This rule is especially important in large projects, for the creators of all extensions, plugins and programming libraries.

For sure, each of you had to change the class along with the changing requirements of the application. Thanks to this principle, your project will be flexible and easy to expand.

While sticking to the first SOLID principle, we must also stick to the second, so let’s move to practice.

In our store logic according to the second SOLID principle, this piece of code is bad:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

privatevoidCheckTypeProducts(stringtypeproduct)

{

if(typeproduct=="candys")

{

GetCandysCount();

}

elseif(typeproduct=="drink")

{

GetDrinksCount();

}

}

privatevoidGetCandysCount()

{

NumberCandys+=1;

}

privatevoidGetDrinksCount()

{

NumberDrinks+=1;

}

Why? Let’s add a new type of product, eg fruit, so now it will look like

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

privatevoidCheckTypeProducts(stringtypeproduct)

{

if(typeproduct=="candys")

{

GetCandysCount();

}

elseif(typeproduct=="drink")

{

GetDrinksCount();

}

elseif(typeproduct=="fruit")

{

GetFruitsCount();

}

}

privatevoidGetCandysCount()

{

NumberCandys+=1;

}

privatevoidGetDrinksCount()

{

NumberDrinks+=1;

}

privatevoidGetFruitsCount()

{

NumberFruits+=1;

}

We had to change the CheckTypeProducts method and add a new variable and another method, so we violated the open-closed SOLID principle, polymorphism and inheritance are often good in such situations.

Correctly, it should look like this:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

namespaceOpenClosed

{

abstractclassTypeProduct

{

publicabstractvoidGetTypeProductsCount(intquantity);

}

classFruit:TypeProduct

{

publicintNumberFruits{get;set;}

publicoverridevoidGetTypeProductsCount(intquantity)

{

NumberFruits+=quantity;

}

}

classDrink:TypeProduct

{

publicintNumberDrinks{get;set;}

publicoverridevoidGetTypeProductsCount(intquantity)

{

NumberDrinks+=quantity;

}

}

classCandy:TypeProduct

{

publicintNumberCandys{get;set;}

publicoverridevoidGetTypeProductsCount(intquantity)

{

NumberCandys+=quantity;

}

}

classClientCart

{

publicList<string>Products{get;set;}

publicList<string>TypesProducts{get;set;}

publicintNumberProducts{get;set;}

publicdoublePriceAllProducts{get;set;}

publicvoidSaveTypeProducts(stringtypeproduct)

{

if(TypesProducts.Contains(typeproduct))

{

Console.WriteLine("This type of product is already in the cart!");

}

else

{

TypesProducts.Add(typeproduct);

}

}

publicvoidGetTypeProductsCount(TypeProduct typeproduct,intquantity)

{

typeproduct.GetTypeProductsCount(quantity);

}

//another methods

}

}

Efficiently using polymorphism and inheritance, we took care of the correctness of the open-closed principle, now if we would like to add some type of product again, it would be enough to add a new class and we would not have to change nothing in existing classes and methods.

However, one more thing is wrong here – the constructor in the Client class, we do not want to add new arguments to it, new variables, moreover, if such a constructor had 50 arguments, it would look … in a word … tragic and illegible, readable constructor together with the rest of the code it should look like this:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

namespaceOpenClosed

{

classClient

{

publicstringFirstName{get;set;}

publicstringLastName{get;set;}

publicstringZipCode{get;set;}

privatedouble_myWallet;

publicdoubleMyWallet{get=>_myWallet;set=>_myWallet=value;}

publicClient(ClientConstructor myconfiguration)

{

FirstName=myconfiguration.firstname;

LastName=myconfiguration.lastname;

ZipCode=myconfiguration.zipcode;

_myWallet=myconfiguration.myWallet;

}

}

publicclassClientConstructor

{

publicstringfirstname;

publicstringlastname;

publicstringzipcode;

publicdoublemyWallet;

}

//Shop class without changes

abstractclassTypeProduct

{

publicabstractvoidGetTypeProductsCount(intquantity);

}

classFruit:TypeProduct

{

publicintNumberFruits{get;set;}

publicoverridevoidGetTypeProductsCount(intquantity)

{

NumberFruits+=quantity;

}

}

classDrink:TypeProduct

{

publicintNumberDrinks{get;set;}

publicoverridevoidGetTypeProductsCount(intquantity)

{

NumberDrinks+=quantity;

}

}

classCandy:TypeProduct

{

publicintNumberCandys{get;set;}

publicoverridevoidGetTypeProductsCount(intquantity)

{

NumberCandys+=quantity;

}

}

classClientCart

{

publicList<string>Products{get;set;}

publicList<string>TypesProducts{get;set;}

publicintNumberProducts{get;set;}

publicdoublePriceAllProducts{get;set;}

publicvoidSaveTypeProducts(stringtypeproduct)

{

if(TypesProducts.Contains(typeproduct))

{

Console.WriteLine("This type of product is already in the cart!");

}

else

{

TypesProducts.Add(typeproduct);

}

}

publicvoidGetTypeProductsCount(TypeProduct typeproduct,intquantity)

{

typeproduct.GetTypeProductsCount(quantity);

}

}

//Order class without changes

}

As you can see now adding arguments in the constructor does not fully comply with the open-closed principle because we have to adding new variables anyway, but it still looks better than the previous constructor. Of course, there is also something like Builder Fluent Interface, which allows you to write the constructor even more readily, but later about it, as we will discuss the Builder design pattern.

Finally, the call in the Main method:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

namespaceOpenClosed

{

classProgram

{

staticvoidMain(string[]args)

{

doubleMyWallet=30;

Client client=newClient(newClientConstructor

{

firstname="Slawomir",

lastname="Kowalski",

zipcode="81-198",

myWallet=MyWallet

});

ClientCart clientCart=newClientCart();

clientCart.Products=newList<string>();

clientCart.TypesProducts=newList<string>();

Shop shop=newShop(100);

shop.NewClient();

clientCart.AddProduct("cola");

clientCart.AddProduct("sprite");

clientCart.SaveTypeProducts("drink");

clientCart.SaveTypeProducts("drink");

Drink drinksInBag=newDrink();

drinksInBag.GetTypeProductsCount(2);

clientCart.SumProductsPrice(5.6);

clientCart.SumProductsPrice(3.5);

clientCart.AddProduct("candy");

clientCart.SaveTypeProducts("candys");

Candy candysInBag=newCandy();

candysInBag.GetTypeProductsCount(1);

clientCart.SumProductsPrice(1.4);

clientCart.AddProduct("orange");

clientCart.SaveTypeProducts("fruit");

Fruit fruitsInBag=newFruit();

fruitsInBag.GetTypeProductsCount(1);

clientCart.SumProductsPrice(2.2);

Order order=newOrder();

Console.WriteLine(order.GiveOrder()+

"\nThe number of products in the cart: "+clientCart.GetProductsCount()+

Summary

And that’s all about the open-closed principle, I hope you have seen how useful it is, the importance of it increases the flexibility, the ease of expansion and reduces the number of bugs in the future expansion of the project.