In Depth Look: Strategy Design Pattern, Dependency Injection (DI), Open/Closed Principle (OCP) and Loose Coupling

Introduction

Most of us involved in key architectural design must have looked over the web to identify and understand core design principles. I have been recently reading various papers over the web explaining various principles, good scalable architectural patterns, to address several design challenges. Most of the papers are well-written and have examples elaborating various principles but it took me a while to get the depth of it. The reason for the challenges is that I began to understand some of the concepts were not “How was that implemented?” but rather “Why is that implementation better?”. This is one of the reasons I felt forced to write this article.

Why to explain it again

As the title of this article suggests, we will be explaining the Strategy Design Pattern, Dependency Injection, Open/Closed principle and Loose Coupling. But my intent is not to just show you how they are implemented, but rather why that implementation makes sense. And the best way is to:

  • Take a real life Scenario.
  • Write a program to implement cover the real life scenario.
  • As the cause an effect of SDLC, we will introduce a change by extending some of the features and will see that whatever we have implemented an initial stage whether or not it is scoring good on the Open Close Principle (OCP).
  • Then we will revise the design and inspect the scalability.
  • And it will make a final conclusive note.

Important Note: The idea here is to get the concept right and not the so-called “Real Life Scenario” to be practically exists or not. Also, throughout the discussion, some of the implementation may not stand right with respect to other principles because I don't want to make the actual discussion too complex by introducing the stuff that is not applicable to this discussion. For a better understanding, I would also encourage you to download the working solution to understand the code better.

strategy


Problem Statement

A Renowned Corporate Group is planning to promote their employees in one of their organizations. In this organization, employees have their ID, Name, YearsSinceLastPromotion, NoOfSkillCertificate and PercentageOfGoalAchieved pre-populated. Management has asked the HR department to list the Employees based on Years since Last Promoted greater than 4.”.

We have tried to make the scenario much simpler.

Implementation 1


Since the request arrived to us by the IT Department, we build a small program to list the items. Let's have a look.

IT Department

In a simple Implementation, we have built the Employee class, the HumanResource class is responsible for holding the List of Employees and will have a method called GetPromotionEligibleEmployees() that will internally create the object of class BasedOnYearsSinceLastPromotion to identify the Employee eligible for promotion. Let's have a look at the actual code.

look at actual Code

Our Promotion Strategy class:

Promotion

Whereas the Main() program along with the Utility function Print() to display employees.

Utility function Print

And finally we run the code. Bingo!

run the code

We have the output and the code works well. We run through the approval cycle and without much hurdle we were able to push the code to production.

Meanwhile in the senior management meeting:

“We decided that we still want to identify the employee with Years since Last Promotion but we may revisit this decision to promote employees based on no Skill Certificate that they have since it will help to bring the most skilled organization image in the market and we want to encourage that.
”.

In short, it is eminent that the IT department has (we have) to modified the program to support the current strategy (in other words based on Years Since Last Promotion) as well as open to adopt the new strategy (in other words based on no skill certificate completed). And hence we will modify our program, the result of which is the Implementation, not 2.

Implementation 2

We still have all the properties (especially NoOfSkillCertificate) that we need for implementing the new features that management is deciding to implement in the current program. Hence we don't need any change in the Employee class.

features that management

As you can see we have introduced the following two new items.

  1. New BasedOnNoOfSkillCertificate Strategy class that will use the NoOfSkillCertificate property to an Employee object to determine the promotion eligibility of an Employee.

  2. An enumeration that will help the client function to choose among various promotions strategies such as BasedOnYearsSinceLastPromotion or BasedOnNoOfSkillCertificate.

    BasedOnYearsSinceLastPromotion

  3. And we are modifying 2 new existing items, the HumanResource class and the Main() function to support new strategies based on Promotion Strategy Enumeration.

    function

    Promotion Strategy Enumeration

With these changes (2 new additions and 2 modifications) we run the program and the code works fine. So the output is:

So the Output

We have created the code that compiles fine, executes fine and gives reasonably decent output. But for our discussion, only output is not our intention. As said earlier, we want to re-evaluate our program scalability. In the Implementation 2, there are several problems.

The problem with Implementation 2

  • In order to implement the change, we've added the new Strategy class and invoked the strategy from the client program and that is absolutely fine. But why do we need to change the HumanResource class? The reason for the change in the HumanResource class is because of its Tight Coupling with the Strategy classes. Having said that, it utilizes the concrete implementations of Strategies.
  • Second, if we want to add new strategies in the future, we will again modify the HumanResource class as we discussed in the first point but this leads to expanding the HumanResource class unnecessarily without any new feature addition. Please note that no matter how many promotion strategies we plug into the HumanResource class, the feature is still single. That is to identify the Eligibility for promotion. So ideally that class should not grow. But with our current implementation, with every new strategy, it will grow.

We want to target the problem that we have with the implementation 2 with some core software development principles. But before that, let's have a look at these principles.

Open/Closed principle (OCP)

According to the definition of the Open/Closed principle (OCP) in the Wikipedia:

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

In our example, the HumanResource class should be able to support additional promotion strategies without any modification.

Decency Injection (DI)

According to the Wikipedia:

Dependency injection is a software design pattern in which one or more dependencies (or services) are injected, or passed by reference, into a dependent object (or client) and are made part of the client's state.

To simplify, we need to provide the reference from inside to the dependencies from outside the actual dependency creation. And In our case, we need to provide a reference to Strategies from the client program and isolating the responsibility from the HumanResource class.

How to fix the problem

To fix the problem, we need to rewrite our code component in the following way:

  • Unify the strategy classes BasedOnYearsSinceLastPromotion and BasedOnNoOfSkillCertificate by implementing the common interface to bring it to the family of classes.

  • And separate the logic of object creation of the strategy classes (BasedOnYearsSinceLastPromotion and BasedOnNoOfSkillCertificate) from the HumanResource class. To do that, we need to introduce Dependency Injection (DI). To be more precise, we need to loosely couple the HumanResource class by providing the interface reference compared to the earlier implementation of tight coupling.

The outcome of this is a Strategy Pattern.

The following is the final implementation with the Strategy Pattern:

Final implementation

Let's look at the code to get a clear understanding.

code to clear

As you can see the, both BasedOnYearsSinceLastPromotion and BasedOnNoOfSkillCertificate implements IPromotionStrategy. And hence they will be loosely coupled with HumanResource. Let's have a look.

HumanResource

And my favorite part, the amount of clutter that we cleaned up from the GetPromotionEligibleEmployees() function. Also the Main() client function is much more mature.

GetPromotionEligibleEmployees

And the output, Why not?

output

Important Question

Is the new Strategy Design Pattern approach addressing the concerns that we raised earlier in this discussion?

The simple answer is “Let's try”. We will introduce another promotion strategy. Isn't it insane?

“We also want to identify the employee eligibility based on the Percentage of Goal achieved.”

Remember, we already have the field PercentageOfGoalAchieved. So the new strategy class is:

PercentageOfGoalAchieved

And in the Main() add a call to the new promotion strategy call.

Main

And the following is the result.

result

Conclusion

Throughout this exercise we ran through various change cycles to understand the way software components behave. We have implemented the Strategy Design Pattern for real-life problems and with a concise approach we looked at various concepts, Dependency Injection (DI), Open/Closed principle (OCP) and Loose Coupling vs Tight coupling. Most importantly, we have analyzed the drawback to some of the approaches that does not adhere to these principles and then we finally implemented the Strategy Design Pattern. We have not only learned how to implement the Strategy Design Pattern but we also looked at why the implementation with the Strategy Design Pattern is better. Experts, let me know if you have any comments that help me refine this article. Your comments are most welcome.

Happy Learning!


Similar Articles