Design Patterns in Swift: A Deep Dive into the Strategy Pattern
Hi folks, sometimes when we are coding, we feel like we are stuck and we know that adding new lines gets our code worse. In the beginning, before meeting design patterns, I couldn’t think future of my code, actually I really didn’t care. But somehow, after some time I really want to be a pragmatic programmer and use best practices for my own benefit and my colleagues. After that I met with strategy patterns, yes pattern(s) there are several of them, and I understand that before writing code, I should think problem and solution and the best form to do it. Yes, form. We need to decide and give a shape to our code to be more understandable, more generic, and more flexible. The best way to do it is by choosing a suitable architectural design pattern. Today I will show you one of my favorite patterns which is Strategy Pattern.
What is a Strategy Pattern?
Encapsulates algorithms in classes, making them reusable and interchangeable at runtime.
What should we get from this expression? When I first see this expression I imagine the payment process. There will be different payment methods(cash, credit card, check, etc.) and each method has different needs. Before this pattern, I would love to use if/else statements to separate these payment methods. However, adding new payment methods increases the complexity of our code. At this moment, the Strategy Pattern comes in.
When and Where should we use it?
- 1️⃣ Different ways to do the same
- 2️⃣ Instead of inheritance
- 3️⃣ Alternative to if/else blocks
The question When? and Where? Is it important right? I know this pattern but where and when can I use it? I was actually asking this question all the time when I was getting used to it. I marked these 3 substances to guide me when I want to use this pattern.
1️⃣ Different ways to do the same
If I see that my code is doing the same thing where the methods are different and after adding lines to it. I asked myself, how I re-organized my methods and how to switch between them. I guess this question directed me to the “Different ways to do the same” conclusion which belongs to the Strategy Pattern.
2️⃣ Instead of inheritance
Normally, when we get a task we create a shell and we do our feature under this shell. However, if our several shells serve the same purpose? What happens? Are we stopping to feed these shells? I guess not. Before doing this I started to ask myself How can I use something that replaces the inheritance? It helps me to find the header “Instead of inheritance” and also the Strategy Pattern.
3️⃣ Alternative to if/else blocks
If/else, switch blocks… When a problem comes, the basic escape point of the developer 🙂 from my perspective. I’m trying to use this pattern as an alternative to if/else blocks. When I asked myself how? I found this pattern and it works 🙂. I will show this in the next header.
How should we use it?
I will show you how can we use this pattern as an easy example. But before that, I want to show you what we are staying away from.
Here in this basic example, you can see that we have different payment types and we do all our operations by controlling if/else blocks. This is an open-ended issue, in the future most probably we will have new payment types and these blocks will grow. At this point, our pattern gives us a solution for this and others. Let’s look together :)
- We have a protocol that includes our general method and variable that all our payment types need.
- After that, we need to create a Class for each payment method to give their specifics that come from our general PaymentMethod protocol.
- After, we created a PaymentOption class to handle all the PaymentMethod like a coordinator.
- Finally, we create a payment option and give CashPayment(), we reached .pay() and type variable of our CashPayment class.
Yes, by using this pattern we can see that we can handle different types of payments without if/else blocks, and its very quite easy to change anything about each payment method strategy.
What are the pros and cons?
Prons
- You can swap algorithms used inside an object at runtime.
- You can isolate the implementation details of an algorithm from the code that uses it.
- You can replace inheritance with composition.
- Open/Closed Principle. You can introduce new strategies without having to change the context.
Cons
- If you only have a couple of algorithms and they rarely change, there’s no real reason to overcomplicate the program with new classes and interfaces that come along with the pattern.
- Clients must be aware of the differences between strategies to be able to select a proper one.
- A lot of modern programming languages have functional type support that lets you implement different versions of an algorithm inside a set of anonymous functions. Then you could use these functions exactly as you’d have used the strategy objects, but without bloating your code with extra classes and interfaces.
Conclusion
In summary, the Strategy Pattern provides a solution for designing systems with interchangeable algorithms or behaviors. Encapsulating each algorithm in separate strategy classes enables dynamic algorithm swapping, isolates implementation details, and supports the Open/Closed Principle. While it may be overcomplicated for simple scenarios, it excels in cases involving varied algorithmic approaches, composition over inheritance, and as an alternative to complex if/else blocks.