Wherever I go, I look for Patterns.

Strategies are important!

Omkar Nath Mandal
Level Up Coding

--

Photo by Mitya Ivanov on Unsplash

Design Patterns are like ‘Super Powers’. Sometimes, I think I have some of these powers but the problem is, I struggle with when to use which power. Maybe this is because of my experience as an engineer. So, this time I thought of going through each of these superpowers, drill down and have a solid understanding of each one of them and document everything in a clear and easy to follow manner. Most of the learning will be from Head first Design Patterns book with some additional resources to help grasp the pattern. Basically for each pattern, I will try to answer the following questions:

What problem does it solve?

What is the pattern exactly?

What are some real-life examples?

What’s the fancy definition to impress someone as a coder?

Let’s talk about the problem

We will begin by understanding some basics and the problem before we dive into the pattern. I come across a line very frequently whenever I read any of the blogs related to Object Oriented Design, which is ‘ Prefer composition over inheritance ’. But we have been taught all good things about Inheritance right? Like

  • Code Reusability
  • Code Readability
  • Reducing Code Redundancy

Now, let’s take a very trivial example (not related to coding) to understand why inheritance can be bad.

From the above class diagram, we can see that by default Gangster ‘is-a’ human and He also wants his kid to inherit his behaviour, his properties despite of the fact that whether the child really wants to become a gangster or not.

See! how inheritance can be a problem. What if the child doesn’t want to inherit everything from their parent. His kid doesn’t want to become a gangster. He wants a simple life. He wants to go to college, he wants to make friends, he wants to become a respectable human being.

Well, He can always do that because we are humans and humans are not bound by laws of ‘Software Engineering’.

Now let’s take an example straight out the head first book. Consider a Duck Simulation Game, where you have various types of ducks with some usual ducky behaviour like a duck can make some sound, they can swim and some ducks can fly. I am not sure if ducks can really fly 😂 but let’s consider this. So we have an initial class hierarchy like below:

So, we can see that there is a parent class Duck and all types of Ducks are inheriting from this Duck class along with all the behaviour and properties which a duck can have. At first nothing seems off here with the use of inheritance. Everything makes sense.

But only thing constant in life of a software is change

One fine day a requirement came which asks the team to introduce a new type of duck lets say ‘Rubber Duck’.

Now we have the option to just add a new class RubberDuck and it will extend duck and everything will be fine. Right???

No, As you might have already thought a rubber duck doesn’t fly. I mean we can program it to fly 😂 but the actual problem is, may be the rubber duck doesn’t want to fly. It doesn’t want to inherit this property from its parent. Again we have come to the same situation as discussed in my previous example i.e,

The child wants to have its own identity. They want to have some behaviour specific to themselves. They just don’t want to inherit everything blindly from their parents. Please don’t force them to❤️

One quick way to solve the problem is to override the fly method in RubberDuck class

But we will soon realise that this can become messy in future because of the following reasons:

  1. Adding new duck behaviour to our Duck class breaks the Open Closed Principle which says Objects or entities should be open for extension but closed for modification. Here to add a new behaviour we will need to change the code in Duck.
  2. Overriding the behaviour in lot of child classes is not a great practice. Let’s say there will be above 1000 types of Ducks in future. It could become a nightmare to maintain such code.

Another solution which we can think of is take out the behaviours out of Duck classes and create interfaces for each kind of behaviour like Fly, Quack, Swim etc. Lets look the class diagram to better understand this

This solution also leads to misery as now we are segregating too much in interfaces/contracts. Each class implementing the interface will have to write logic for each kind of behaviour. There will be lot of code duplication.

Now we will look at what is the pattern/problem exactly where which needs attention

We can see from above example, that there are some kind of behaviour which can be performed in different ways but the end result is same. We can interchange the word ‘behaviour’ with algorithms/strategies and these algorithms/strategies can have different implementation.

So, what we can do is take this set of algorithms. Encapsulate them. In the above example we can take out Flying behaviour and quacking behaviour out of duck class and encapsulate them. Let’s look at the class diagram of final refactored code.

Let us understand what above diagram shows:

  • Here we have created a FlyBehaviour interface and there are concrete flying behaviours which implements this.
  • The Duck class no more tightly coupled with the fly or any behaviour.
  • The child duck classes can choose their way of flying at runtime.

Enough of analogies , show me some real life engineering problems where we can use this😒

Just bear with me if things are not that clear. We will go through tons of examples to understand this completely😊

Example 1: We all love shopping right? Personally I love scrolling Myntra, Amazon, flipkart and see things I can’t afford. That’s another misery. But let’s consider any shopping app. What happens when we want to make a purchase. We usually have 3–4 options to choose from of how we want to make the payment. It can be via:

  • Google Pay
  • Phone Pay
  • Paytm
  • RazorPay
  • Pay by Debit Card
  • Pay by Credit Card

Now think the end result is same. The end outcome is we want to pay to the vendor so that we can buy our favourite things. The way how we want to pay differs. Again, we can see that we are in same situation right? We can encapsulate the set of algorithms and client can decide which one it wants to use. Enough of class diagrams. Let’s do some coding 🥷

Well that’s a whole lot of code. Let us understand what we actually did. We took parts of the code which can change frequently and encapsulated it under a different umbrella. Now adding a new Payment Method is really simple. Just create a Class and implement the PaymentMethod interface and then client can just use the newly added method.We can always argue that what’s the benefit of this. I can use if/else blocks for different types of payments. Well I would say there is no boundary or threshold for bad/good coding practice 😁 The whole idea of using patterns is to make code understandable, extensible, maintainable. If we think adding a new payment method is just a matter of adding additional if statement then we are again breaking ‘Open-Closed Principle’ .

Example 2: Now let’s consider another scenario where we are still using the shopping website. But this time we will talk about how we will login into that shopping website. We generally have options to login into the application:

  • Login via google
  • Login via facebook
  • Login via snapchat
  • Login via twitter

Again, the end result is same. We want to login into the application but we can choose different ways to do that. Let’s look at the code again:

Now we see how we identified the situation and pattern and followed same procedures to write beautiful code. We can always integrate new Login methods just by implementing the LoginMethod interface without modifying existing code.

Example 3: Now we will take another example. Consider a scenario when we want to travel between city A to city B and there are various modes of transportation available:

  • Travel via cab
  • Travel via flight
  • Travel via train
  • Travel via bus

Does this situation ring a bell ? 🐥

Yes! It’s the same situation as earlier. The end goal is to reach City B from A and there are various modes of transportation to do that. Let’s look at the code again.

We have successfully identified some of the common scenarios where we can utilise this pattern. I think we are now better equipped with the understanding of the pattern and situation where it is useful. Let’s take one final example 😁

Example 4: This time let’s build a logging system. We know how important logs are while debugging production issues right. So, let us try to identify following:

What strategies we can encapsulate?

  • We know logging can use any kind of logger. One logger can simply write to file and keep the file in the developer’s machine.
  • Another one can upload logs to a cloud provider service like S3.
  • Another one can store logs to a relational DB.

So, we can see that we have couple of ways to do logging and end goal is same for all i.e, We want logs to be generated. We can make use the Strategy Pattern as we have done in previous examples. Let us look at the code again:

Finally, We can see how this pattern is just beautiful and how we are able to use it in different situations.

So what’s the official definition for this pattern?

According to GOF book, “Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it”

That’s it. We have successfully covered Strategy Pattern. Please go through the references for better understanding and please don’t hate me if you find anything wrong 🥺 .I am trying to do better ❤️

References:

--

--