Easily solve interface incompatibility issues using the Adapter Pattern
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 Adapter Pattern.
Common scenario
In the web system, mail service is a very commonly used service. On the Node.js platform, we can use the nodemailer module to easily implement email sending functionality. After successfully installing nodemailer module, you can follow below steps to send email:
1 2 3 | <span class="token keyword">let</span> transporter <span class="token operator">=</span> nodemailer <span class="token punctuation">.</span> <span class="token function">createTransport</span> <span class="token punctuation">(</span> transport <span class="token punctuation">[</span> <span class="token punctuation">,</span> defaults <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> transporter <span class="token punctuation">.</span> <span class="token function">sendMail</span> <span class="token punctuation">(</span> data <span class="token punctuation">[</span> <span class="token punctuation">,</span> callback <span class="token punctuation">]</span> <span class="token punctuation">)</span> |
To avoid binding the mail service to a specific service provider , before developing the mail service , we define the interface related to the mail provider :
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">interface</span> <span class="token class-name">EmailProvider</span> <span class="token punctuation">{</span> <span class="token function">sendMail</span> <span class="token punctuation">(</span> options <span class="token operator">:</span> EmailOptions <span class="token punctuation">)</span> <span class="token operator">:</span> Promise <span class="token operator"><</span> EmailResponse <span class="token operator">></span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">interface</span> <span class="token class-name">EmailOptions</span> <span class="token punctuation">{</span> to <span class="token operator">:</span> string <span class="token operator">|</span> string <span class="token punctuation">[</span> <span class="token punctuation">]</span> <span class="token punctuation">;</span> subject <span class="token operator">:</span> string <span class="token punctuation">;</span> html <span class="token operator">:</span> string <span class="token punctuation">;</span> from <span class="token operator">?</span> <span class="token operator">:</span> string <span class="token punctuation">;</span> text <span class="token operator">?</span> <span class="token operator">:</span> string <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">interface</span> <span class="token class-name">EmailResponse</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> |
With these interfaces, we can easily create a mail service :
1 2 3 4 5 6 7 8 | <span class="token keyword">class</span> <span class="token class-name">EmailService</span> <span class="token punctuation">{</span> <span class="token function">constructor</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token keyword">public</span> emailProvider <span class="token operator">:</span> EmailProvider</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">sendMail</span> <span class="token punctuation">(</span> options <span class="token operator">:</span> EmailOptions <span class="token punctuation">)</span> <span class="token operator">:</span> Promise <span class="token operator"><</span> EmailResponse <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> emailProvider <span class="token punctuation">.</span> <span class="token function">sendMail</span> <span class="token punctuation">(</span> options <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> result <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
For now, this solution is not without problems, but if one day we need to use a third-party email cloud service provider . Such as sendgrid or mailersend , etc. You will see that the name of the function that the SDK uses to send mail is send
. So we go ahead and define an Interface CloudEmailProvider
:
1 2 3 4 | <span class="token keyword">interface</span> <span class="token class-name">CloudEmailProvider</span> <span class="token punctuation">{</span> <span class="token function">send</span> <span class="token punctuation">(</span> options <span class="token operator">:</span> EmailOptions <span class="token punctuation">)</span> <span class="token operator">:</span> Promise <span class="token operator"><</span> EmailResponse <span class="token operator">></span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Comparing the previously defined Interface EmailProvider
, you will see the following problem:
Therefore, we cannot directly use EmailService
to access third-party email cloud service providers . To solve this problem, there are many ways. Let’s see how to use the Adapter Pattern to solve the above problem.
Adapter Pattern
The purpose of the Adapter Pattern is to allow two objects to not work together because the Interface does not match. It’s like glue, transforming different things so they can work together. Or the easiest to imagine is when you come to Japan all sockets are flat ends => we need a corresponding Adapter to handle this.
In the example above, the Adapter Pattern contains the following roles:
- Client(EmailService) : The object that needs to use the Target interface;
- Target(EmailProvider) : Defines the Interface that the Client expects;
- Adapter(CloudEmailAdapter) : Adjust Adapter Interface to Target interface;
- Adapter(CloudEmailProvider) : Defines the Interface that needs to be adjusted.
After going through some of the work required to apply the Adapter Pattern to this example, let’s first create CloudEmailAdapter
class:
1 2 3 4 5 6 7 8 | <span class="token keyword">class</span> <span class="token class-name">CloudEmailAdapter</span> <span class="token keyword">implements</span> <span class="token class-name">EmailProvider</span> <span class="token punctuation">{</span> <span class="token function">constructor</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token keyword">public</span> emailProvider <span class="token operator">:</span> CloudEmailProvider</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">sendMail</span> <span class="token punctuation">(</span> options <span class="token operator">:</span> EmailOptions <span class="token punctuation">)</span> <span class="token operator">:</span> Promise <span class="token operator"><</span> EmailResponse <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> emailProvider <span class="token punctuation">.</span> <span class="token function">send</span> <span class="token punctuation">(</span> options <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> result <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
In the above code, because the two Interfaces of EmailProvider
and CloudEmailProvider
do not match, we create a class CloudEmailAdapter
to solve the compatibility issue.
Next, we take the sendgrid
service as an example to implement SendgridEmailProvider
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">import</span> <span class="token punctuation">{</span> MailService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@sendgrid/mail"</span> <span class="token punctuation">;</span> <span class="token keyword">class</span> <span class="token class-name">SendgridEmailProvider</span> <span class="token keyword">implements</span> <span class="token class-name">CloudEmailProvider</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> sendgridMail <span class="token operator">:</span> MailService <span class="token punctuation">;</span> <span class="token function">constructor</span> <span class="token punctuation">(</span> <span class="token parameter"><span class="token keyword">private</span> config <span class="token operator">:</span> <span class="token punctuation">{</span> apiKey <span class="token operator">:</span> string <span class="token punctuation">;</span> from <span class="token operator">:</span> string <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">this</span> <span class="token punctuation">.</span> sendgridMail <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MailService</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> sendgridMail <span class="token punctuation">.</span> <span class="token function">setApiKey</span> <span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> config <span class="token punctuation">.</span> apiKey <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">send</span> <span class="token punctuation">(</span> options <span class="token operator">:</span> EmailOptions <span class="token punctuation">)</span> <span class="token operator">:</span> Promise <span class="token operator"><</span> EmailResponse <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span> <span class="token punctuation">.</span> sendgridMail <span class="token punctuation">.</span> <span class="token function">send</span> <span class="token punctuation">(</span> options <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">return</span> result <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Hint: The above code is just to show you how to use the Apply Pattern and needs to be adjusted accordingly when used in real projects.
Now that the SendgridEmailProvider
and CloudEmailAdapter
classes are defined, let’s see how to use them:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">const</span> sendgridMail <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SendgridEmailProvider</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> apiKey <span class="token operator">:</span> <span class="token string">"******"</span> <span class="token punctuation">,</span> from <span class="token operator">:</span> <span class="token string">"bytefer@gmail.com"</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">const</span> cloudEmailAdapter <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">CloudEmailAdapter</span> <span class="token punctuation">(</span> sendgridMail <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token keyword">const</span> emailService <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EmailService</span> <span class="token punctuation">(</span> cloudEmailAdapter <span class="token punctuation">)</span> <span class="token punctuation">;</span> emailService <span class="token punctuation">.</span> <span class="token function">sendMail</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> to <span class="token operator">:</span> <span class="token string">"******"</span> <span class="token punctuation">,</span> subject <span class="token operator">:</span> <span class="token string">"Adapter Design Pattern"</span> <span class="token punctuation">,</span> html <span class="token operator">:</span> <span class="token string">"<h3>Adapter Design Pattern</h3>"</span> <span class="token punctuation">,</span> from <span class="token operator">:</span> <span class="token string">"bytefer@gmail.com"</span> <span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
=> Now, no matter what service, you can use the sendMail
function.
External example: Computers often have an Adapter sac => via Japan 110V or back to VN 220V … as long as there is a charging Adapter, everything is the same, just perform the action of plugging it into an electrical outlet.
Finally, the usage scenarios of the Adapter Pattern:
- The system needs to use an existing class and the interface of this class does not meet the system’s needs, that is, the interface is not compatible;
- Use a service provided by a third party, but the interface definition of this service is different from your interface definition.
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.