DEV Community

Cover image for JS and Design Patterns - Chapter 6 πŸš€
devlazar
devlazar

Posted on

JS and Design Patterns - Chapter 6 πŸš€

Table Of Contents
* πŸ€“INTRODUCTION
* πŸ”ŒABOUT ADAPTER PATTERN
* πŸ“ŠSTOCK MARKET EXAMPLE
* πŸ‘€VISUAL REPRESENTATION
* πŸ§ͺCHEMICAL COMPOUND EXAMPLE
* βš’ APPLICABILITY
* βœ…PROS
* ❌CONS
* πŸ™THANK YOU

πŸ€“ INTRODUCTION

Welcome, my dear codedudes and codedudettes, to yet another Codespresso JS and Design Patterns blog! πŸ‘©β€πŸ’» I hope you are all having a great day! Before we start I want to remind you that I am posting (almost daily) blogs about Computer Science and Computer Programming right here on Dev.to; If you have any ambiguities, or even If you just want to connect you can follow me, and contact me via 🐀Twitter, LinkedIn, β˜• ko-fi or via E-mail. I am here for you to talk, discuss, maybe show you some pointers, especially for the coding newbies.. Also, would like to connect, learn from you, or maybe teach you something, and just have fun by doing what I love.

Today, we are talking about the Adapter Pattern. πŸš€

adapt

πŸ”Œ ABOUT ADAPTER PATTERN

The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to collaborate. It can convert an interface of the class into an interface that another class expects. That way it allows classes to function together which normally couldn't be possible due to their incompatible interfaces.

πŸ“Š STOCK MARKET EXAMPLE

Imagine that you need to create a stock market monitoring application. The app downloads the stock data from multiple sources in XML format and then displays nice-looking charts and diagrams for the user. At some point, you decide to improve the app by importing some 3rd-party analytics library. But, you encounter the problem. The 3rd-party library only works with data in JSON format.

πŸ€” What should we do?

  • Proposition: We could change the library to work with XML.

Yes, we could do it. But that might break some existing code that relies on the library, or worse, you might not have the library's source code in the first place, making this approach impossible.

πŸ’‘ REVELATION
To solve this problem, we could create an adapter. The special object that converts the interface of one object in a way that another object can understand it. An adapter will wrap one of the objects to hide the complexity of the conversion happening behind the scenes. The wrapped object isn't even aware of the adapter.

βš™ HOW DOES IT WORK?

  • The adapter gets an interface, compatible with one of the existing objects
  • Using this interface, the existing object can safely call the adapter's methods
  • Upon receiving a call, the adapter passes the request to the second object, but in a format and order that the second object expects

In the Stock Market example, we could create XML-to-JSON adapters for every class of the analytics library that your code works with directly. Then, we can adjust our code to communicate with the library only via these adapters. When an adapter receives a call, it will translate all incoming XML data into a JSON structure and it will pass the call to the appropriate methods of a wrapped analytics object.

πŸ‘€ VISUAL REPRESENTATION

Alt Text

  • TARGET - It defines the specific interface that is used by the Client class
  • ADAPTER - It adapts the interface of the class Adaptee towards the interface of the class
  • ADAPTEE - It defines an existing interface that should be adapted
  • CLIENT - It looks after objects that require an interface of the Target class

Let's explain this by using an interesting real-world science example.

πŸ§ͺ CHEMICAL COMPOUND EXAMPLE

This real-world example shows the use of the chemical-compound databank. The complex chemical compounds access the databank via an interface provided by an interface of the Adapter class.

Let's dive into the code. πŸš€ (❗ As always, read the comments)

//Target - It defines the specific interface that is used by the Client class
class Compound{
   //setting up initial valules - self explanatory :)
   constructor(name){
      this.name = name;
      this.bolingPoint = -1;
      this.meltingPoint = -1;
      this.molecularWeight = -1;
      this.molecularFormula = -1;
   }

   //setting compound name
   setCompound(name){
      this.name = name;
   }

   //name getter
   display(){ return this.name; }
}


//Adapter - It adapts the interface of the class Adaptee towards the interface of the class
class RichCompound extends Compound{
   constructor(name){
      super(name);
   }
   /* This function creates Chemical Databank for each 
   new Rich compound that we are creating*/
   display(){
     //creating a new chemical databank
     this.bank = new ChemicalDatabank();
     //getting the boiling point based on the chemical name and indicator B === "Boiling"
     var boilingPoint = this.bank.getCriticalPoint(this.name, "B");
     //getting the melting point based on the chemical name and indicator M === "Melting"
     var meltingPoint = this.bank.getCriticalPoint(this.name, "M");
     //getting the molecular weight based on the chemical name
     var molecularWeight = this.bank.getMolecularWeight(this.name);
     //getting the molecular formula based on the chemical name
     var molecularFormula = this.bank.getMolecularStructure(this.name);

     //displaying all necessary information
     console.log("%c%s",
        "color: black; background: lightgreen; font-size: 24px; border: 1px solid lightgreen; border-radius: 5px; padding: 5px;"
        ,`πŸ§ͺ Name: ${super.display()}`);
     console.log(`πŸ‘©β€πŸ”¬ Formula: ${molecularFormula}`);
     console.log(`πŸ‹οΈβ€β™€οΈ Weight: ${molecularWeight}`);
     console.log(`❄ Melting Pt: ${meltingPoint}\u00B0C`);
     console.log(`πŸ”₯ Boiling Pt: ${boilingPoint}\u00B0C`);
   }
}

//Adaptee - It defines an existing interface that should be adapted
class ChemicalDatabank{
  //databank - taken from the 'legacy API'
  getCriticalPoint(compound, point){
     var temperature = 0.0;
     //freezing point
     if (point == "M"){
        switch(compound.toLowerCase()){
           case "water": temperature = 0.0; break;
           case "benzene": temperature = 5.5; break;
           case "alcohol": temperature = -114.1; break;
        }
     }else{ //boiling point
        switch(compound.toLowerCase()){
           case "water": temperature = 100.0; break;
           case "benzene": temperature = 80.1; break;
           case "alcohol": temperature = 78.3; break;
        }
     }
     return temperature;
  }

  getMolecularStructure(compound){
     var structure = "";
     switch(compound.toLowerCase()){
        case "water": structure = "H2O"; break;
        case "benzene": structure = "C6H6"; break;
        case "alcohol": structure = "C2H6O2"; break;
     }
     return structure;
  }

  getMolecularWeight(compound){
     var weight = 0.0;
     switch(compound.toLowerCase()){
        case "water": weight = 18.015; break;
        case "benzene": weight = 78.1134; break;
        case "alcohol": weight = 46.0688; break;
     }
     return weight;
  }
}

//unadapted compound
var unadaptedCompound = new Compound("Unknown");
console.log("%c%s",
        "color: black; background: red; font-size: 24px; border: 1px solid red; border-radius: 5px; padding: 5px;",`❌ Unadapted compound: ${ unadaptedCompound.display()}`);

//adapted compounds
var water = new RichCompound("Water");
water.display();

var benzene = new RichCompound("Benzene");
benzene.display();

var alcohol = new RichCompound("Alcohol");
alcohol.display();
Enter fullscreen mode Exit fullscreen mode

Pretty interesting, right? 😎 Don't hesitate to play with the code.

βš’ APPLICABILITY

  • You can use the Adapter pattern when you want to use some existing class, but its interface isn't compatible with the rest of your code. The adapter pattern, lets you create a middle-layer class that serves as a translator between your code and legacy class, a 3rd-party library, or any other class with a weird interface.
  • Use the pattern when you want to reuse several existing subclasses that lack some common functionality that can't be added to the superclass. You could extend each subclass and put the missing functionality into new child classes. However, you'll need to duplicate the code across all of these new classes, which is not good.

βœ… PROS

  • Single Responsibility Principle. You can separate the interface or data conversion code from the primary business logic of the program.
  • Open/Closed Principle. You can introduce new types of adapters into the program without breaking the existing client code, as long as they work with the adapters through the client interface.

❌ CONS

  • The overall complexity of the code increases because you need to introduce a set of new interfaces and classes. Sometimes it’s simpler just to change the service class so that it matches the rest of your code.

πŸ™ THANK YOU FOR READING!

References:
School notes...
refactoring

Please leave the comment, tell me about you, about your work, comment your thoughts, connect with me via Twitter or LinkedIn.

β˜• SUPPORT ME AND KEEP ME FOCUSED!
Buy Me a Coffee at ko-fi.com

Have a nice time hacking! 😊

Top comments (0)