How to use Strategy Pattern in TypeScript to solve real world problems in web projects.
Welcome to the Design Patterns in TypeScript series, this series will introduce some useful Design Patterns in web development using TypeScript.
Design Patterns are very important for web developers and we can code better by mastering them. In this article, I will use TypeScript to introduce the Strategy Pattern .
Problem
Register and Login are important features in web applications. When registering a web application, the more common way to register is using account/password, email or mobile number… Once you have successfully registered, you can use the corresponding function to Login .
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">function</span> <span class="token function">login</span> <span class="token punctuation">(</span> <span class="token parameter">mode</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"account"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithPassword</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"email"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithEmail</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"mobile"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithMobile</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
When the web application needs to support other Login functions, for example, in addition to the Login email function, the Login page also supports the Login features of third-party platforms such as Google, Facebook, Apple and Twitter.
So to support more third-party Login function methods, we need to modify the previous login
function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">function</span> <span class="token function">login</span> <span class="token punctuation">(</span> <span class="token parameter">mode</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"account"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithPassword</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"email"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithEmail</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"mobile"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithMobile</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"google"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithGoogle</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"facebook"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithFacebook</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"apple"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithApple</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> mode <span class="token operator">===</span> <span class="token string">"twitter"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">loginWithTwitter</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Doesn’t look good at all! If we continue to add or modify Login methods later on, we will find that this login
function becomes more and more difficult to maintain. For this, we can use the Strategy Pattern to encapsulate different Login functions into different strategies .
Strategy Pattern
To better understand the Strategy Pattern fragment, let’s first take a look at the corresponding UML diagram:
In the image above, we define an Interface Strategy
, then implement two Login strategies for Twitter and account/password based on this Interface.
Interface strategy
1 2 3 4 | <span class="token keyword">interface</span> <span class="token class-name">Strategy</span> <span class="token punctuation">{</span> <span class="token function">authenticate</span> <span class="token punctuation">(</span> args <span class="token operator">:</span> any <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token operator">:</span> boolean <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Strategy Twitter Class is implemented from Interface Strategy
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">class</span> <span class="token class-name">TwitterStrategy</span> <span class="token keyword">implements</span> <span class="token class-name">Strategy</span> <span class="token punctuation">{</span> <span class="token function">authenticate</span> <span class="token punctuation">(</span> <span class="token parameter">args <span class="token operator">:</span> any <span class="token punctuation">[</span> <span class="token punctuation">]</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> token <span class="token punctuation">]</span> <span class="token operator">=</span> args <span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> token <span class="token operator">!==</span> <span class="token string">"tw123"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">error</span> <span class="token punctuation">(</span> <span class="token string">"Twitter account authentication failed!"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">false</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token string">"Twitter account authentication succeeded!"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
The LocalStrategy class is also implemented from Interface Strategy
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">class</span> <span class="token class-name">LocalStrategy</span> <span class="token keyword">implements</span> <span class="token class-name">Strategy</span> <span class="token punctuation">{</span> <span class="token function">authenticate</span> <span class="token punctuation">(</span> <span class="token parameter">args <span class="token operator">:</span> any <span class="token punctuation">[</span> <span class="token punctuation">]</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span> username <span class="token punctuation">,</span> password <span class="token punctuation">]</span> <span class="token operator">=</span> args <span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> username <span class="token operator">!==</span> <span class="token string">"bytefer"</span> <span class="token operator">||</span> password <span class="token operator">!==</span> <span class="token string">"666"</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token string">"Incorrect username or password!"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">false</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> console <span class="token punctuation">.</span> <span class="token function">log</span> <span class="token punctuation">(</span> <span class="token string">"Account and password authentication succeeded!"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
After having implementations from different Interface Strategy, we define an Authenticator class to switch between different Login strategies and perform authentication operations accordingly.
Authenticator class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">class</span> <span class="token class-name">Authenticator</span> <span class="token punctuation">{</span> strategies <span class="token operator">:</span> Record <span class="token operator"><</span> string <span class="token punctuation">,</span> Strategy <span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> <span class="token function">use</span> <span class="token punctuation">(</span> <span class="token parameter">name <span class="token operator">:</span> string <span class="token punctuation">,</span> strategy <span class="token operator">:</span> Strategy</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> strategies <span class="token punctuation">[</span> name <span class="token punctuation">]</span> <span class="token operator">=</span> strategy <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">authenticate</span> <span class="token punctuation">(</span> <span class="token parameter">name <span class="token operator">:</span> string <span class="token punctuation">,</span> <span class="token operator">...</span> args <span class="token operator">:</span> any</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token operator">!</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> strategies <span class="token punctuation">[</span> name <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> console <span class="token punctuation">.</span> <span class="token function">error</span> <span class="token punctuation">(</span> <span class="token string">"Authentication policy has not been set!"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">false</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> strategies <span class="token punctuation">[</span> name <span class="token punctuation">]</span> <span class="token punctuation">.</span> <span class="token function">authenticate</span> <span class="token punctuation">.</span> <span class="token function">apply</span> <span class="token punctuation">(</span> <span class="token keyword">null</span> <span class="token punctuation">,</span> args <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// authenticate.apply là cú pháp apply args của typescript</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Then we can use different Login functions to achieve user authentication in the following ways:
1 2 3 4 5 6 7 8 9 | <span class="token keyword">const</span> auth <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Authenticator</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> auth <span class="token punctuation">.</span> <span class="token function">use</span> <span class="token punctuation">(</span> <span class="token string">"twitter"</span> <span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">TwitterStrategy</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> auth <span class="token punctuation">.</span> <span class="token function">use</span> <span class="token punctuation">(</span> <span class="token string">"local"</span> <span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">LocalStrategy</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">login</span> <span class="token punctuation">(</span> <span class="token parameter">mode <span class="token operator">:</span> string <span class="token punctuation">,</span> <span class="token operator">...</span> args <span class="token operator">:</span> any</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> auth <span class="token punctuation">.</span> <span class="token function">authenticate</span> <span class="token punctuation">(</span> mode <span class="token punctuation">,</span> args <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">login</span> <span class="token punctuation">(</span> <span class="token string">"twitter"</span> <span class="token punctuation">,</span> <span class="token string">"123"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token function">login</span> <span class="token punctuation">(</span> <span class="token string">"local"</span> <span class="token punctuation">,</span> <span class="token string">"bytefer"</span> <span class="token punctuation">,</span> <span class="token string">"666"</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
When you successfully run the above code, the corresponding output is shown in the following figure:
The Strategy Pattern, in addition to being used for authentication Login cases, can also be used in many different scenarios (For example: form validation). It can also be used to optimize problems with too many if/else branches.
If you use Node.js to develop authentication services , then you will normally use this passport.js library, right? If you have used this Passport.js library/Module but don’t know how it works, then Strategy Pattern will help you to understand it better. It’s better to use something you understand than to use something you don’t understand. Ahihi
This passport.js module is very powerful and currently supports up to 538 strategies:
Some situations where you can think about using the Strategy Pattern :
- When a system needs to automatically choose one of the algorithms. And each algorithm can be encapsulated in a strategy .
- Many classes differ only in behavior and it is possible to use the Strategy Pattern to automatically choose specific behavior to be executed at runtime.
Roundup
As always, I hope you enjoyed this article and learned something new.
Thank you and see you in the next posts!
If you find this blog interesting, please give me a like and subscribe to support me. Thank you.