Strategy Pattern Implementation with Typescript and Angular
Design patterns are proven, practical, and reusable solutions fit for tackling specific problems in software development. They not only help us avoid pitfalls in organizing our applications, but also provide a shared glossary to describe our implementation and understand each other as fellow developers. There are dozens of patterns already discovered and I am pretty sure you are using at least some of them, even if you do not identify them as a design pattern (Hello constructor pattern 👋).
Design patterns are grouped into three categories: Creational, structural, and behavioral. In this article, I would like to focus on one of the behavioral patterns, the strategy (a.k.a. policy) pattern, and how we benefit from it in the ABP Framework frontend. I am hoping this article will help you understand and use both the pattern and the ABP features more effectively than ever.
What is Strategy Pattern?
I like explaining concepts with code examples and, since we shall see the use of strategy pattern in Angular later, the code examples here are in TypeScript. That being said, JavaScript implementation is quite similar.
So, let’s check out what the Avengers would look like, if they were represented by a class:
Although it looks OK at first, this class has the following drawbacks:
- It is difficult to
recruit
a newHero
tofight
with otherAvengers
, because you will need to add another case (and probably a new attack type) for the new hero. - It is also difficult to change or remove an existing
Hero
. Consider changing Thor's attack from throwing his hammer, Mjolnir, to summoning a thunder strike. - Each different attack is just a one-liner here, but consider how difficult to read and maintain
Avengers
would become, if they were longer and more complex. Avengers
has to provide an attack for all heroes, although some of them might not be currently in theensemble
. This is not tree-shakable and could lead to a waste of resources.
Strategy design pattern decouples context from an interchangeable algorithm’s implementation by delegating it to another class which is bound by a contract defined via the strategy interface.
So, let’s refactor Avengers
and see that it looks like when strategy pattern is applied:
Instead of creating an interface and implementing it, we have benefited from inheritance here to avoid repetition, but they have the same effect with regard to the strategy pattern.
Let’s check the organization of the new code:
- The
Hero
abstract class provides us the strategy, the contract that guarantees the algorithm is implemented by each hero. We could have used an interface, but an abstract class is beneficial here. - We need concrete strategies which implement the algorithm. In this case, they are the subclasses. Heroes will be instances of these subclasses and they all will have a separate
attack
. - The context will refer to the method guaranteed by the contract when
fight
is called.
Advantages of the Strategy Pattern
There are some advantages we gained by implementing the strategy pattern above:
- The
switch
statement is gone. We no longer need to check a condition to determine what to do next. - It is much easier to understand and maintain
Avengers
now. - It is also easier to test
Avengers
than it was before. Avengers
canrecruit
any newHero
(Spider-Man, Ant-Man, Scarlet Witch, Falcon, etc.) and theirattack
will just work. Therefore, another concrete strategy can always be introduced and the functionality is much more extensible.Avengers
is now able to switch between available strategies at runtime. In other words, it is now capable of replacing anUnarmedHero
with aShootingHero
. Think about Black Widow who can be both.- If a concrete strategy, a subclass of
Hero
, is not used, it could possibly be tree-shaked.
Drawbacks of Strategy Pattern
There are but a few drawbacks which could be associated with strategy pattern:
- The client (consumer of
Avengers
, Nick Furry?) should be aware of the available strategies in order torecruit
the correct one. - There may be some peaceful
Hero
who will notattack
at all, but still has to implement a noop method, just to align with the contract. Think about Bruce Banner (not the Hulk), who can contribute to theAvengers
with his science and not participate in thefight
. - There is an increased number of objects generated. In some edge cases, this can cause an overhead. There is another design pattern, the flyweight pattern, that can be used to lower this overhead.
- There are cases when parameters are passed to methods defined by the strategy. Not all strategies use all parameters, but they still have to be generated and passed.
How the ABP Benefits From the Strategy Pattern
Several services in the ABP Angular packages resort to the strategy pattern and we are planning to refactor more of the existing ones into this pattern. Let’s see how DomInsertionService
is used:
Remarkably, we have an insertContent
method and are passing the content to be inserted to it via a predefined content strategy, CONTENT_STRATEGY.AppendScriptToBody
. Let's check the content strategy:
Well, apparently, ContentStrategy
defines a contract that consumes two other strategies: DomStrategy
and ContentSecurityStrategy
. We are not going to dig deeper and examine these strategies, but the main takeaway here is that the ABP employs a composition of strategies to build complex ones.
Another important information here is that, although there are some predefined strategies exported as a constant (CONTENT_STRATEGY
), using the superclasses and/or constructing different compositions, you can always develop new strategies.
Let’s take a closer look at the DomInsertionService
class:
The insertElement
method of the ContentStrategy
is called and that's pretty much it. The insertion algorithm is delegated to the strategy. As a result, there is not much to read and maintain here, yet the service is capable of doing almost any DOM insertion we may ever need.
Conclusion
The strategy pattern is a proven and reusable way of making your code more flexible and extensible. It also usually leads to a more readable, testable, and maintainable codebase. The ABP Angular services like LazyLoadService
, ContentProjectionService
, and DomInsertionService
already make use of the pattern and we are hoping to deliver more of these services in the near future.
Thank you for reading.
This article was originally published on Volosoft Blog.