DEV Community

Cover image for Design Patterns: Adapter
Tamerlan Gudabayev
Tamerlan Gudabayev

Posted on • Updated on

Design Patterns: Adapter

In the last article, we have finished the first chapter in our design pattern series.

Today, we start a new chapter, where we will go over the structural design pattern.

"But, what's structural?" you may ask.

While creational patterns focus on how objects are created, structural patterns focus on how efficient do objects combine to create other complex objects.

A real-life analogy would be a fruit salad, you can make fruit salads in many different ways with all sorts of different fruits, but there are some combinations that are superior to others. Structural design patterns help use effectively and flexibly combine objects together to form complex structures.

To begin, the first of these patterns would be the Adapter pattern.

Today you will learn:

  • Core concepts behind the Adapter pattern
  • How to implement the adapter pattern
  • How to recognize opportunities the adapter pattern
  • The benefits and downsides of the adapter pattern

Definition

We have a ritual in this series, to start googling things.

So let's google: "what is the adapter pattern?"

You would get a Wikipedia answer that would look something like this:

In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code

Why does Wikipedia make everything sound so damn complicated?

In a nutshell, the adapter pattern allows classes with incompatible interfaces to work with each other.

But what do you mean by incompatible interfaces?

Good question, lemme give you an example

PS. I'm taking this example from refactoring.guru, they are a great resource, and most of my research comes from there. You can check them out here.

Imagine that you have a stock monitoring app.

The app currently has two components:

  • The third-party library you use to get stocks data.
  • A client that monitors, and visualizes the data.

One day you come across a new library, that provides analytics.

You get all excited and begin coding.

You eventually realize that your current code isn't compatible with the analytics library.

Your code, works on XML, while the analytics library works in JSON.

Your stock provider only works with XML, while the analytics only works with JSON.

This may be the end...

Source: https://refactoring.guru/design-patterns/adapter

At this point you have two options:

  1. Get all grumpy and quit
  2. Toughen up and find a solution

For the sake of this article, let's say you went with option number two.

After a while, you come across an article, about the adapter pattern, and soon realize it's a perfect opportunity to use it.

You just have to create an Adapter class, that transforms XML to JSON.

Source: https://refactoring.guru/design-patterns/adapter

The adapter has to wrap around the XML object and transform it into JSON.

The XML class doesn't even know it's being wrapped.

Seems simple enough, let's implement this!

Implementation

Currently, our client looks something like this:

import stock_data_providor from "third-party-stock-library"

class Client {

    public function main(){
        const xml_stocks = stock_data_provider.get_all_stocks()

        this.visualize(xml_stocks)
    }

    public function visualize(){
        // visualization code
    }

}

There are currently only two components, the stock data and client.

Let's try adding the analytics library:

import stock_data_providor from "third-party-stock-library"
import analytics from "third-party-analytics-library"

class Client {

    public function main(){
        const xml_stocks = stock_data_provider.get_all_stocks()

        this.visualize(xml_stocks)
        analytics.analyze(xml_stocks)
    }

    // rest of code

}

This will throw an error because the analyze function only accepts a JSON.

To fix that, we have to transform the XML into JSON.

Let's create an adapter class:

class XmlToJsonAdapter(){
    const xml_data

    constructor(xml_data){
            this.xml_data = xml_data
    }

    public function get_json() {
        // return json version of xml
        // out of the scope of this article 
    }

}

Our adapter class accepts xml_data and with the help of the get_json function, it returns a JSON version of the XML.

Let's update our client:

import stock_data_providor from "third-party-stock-library"
import analytics from "third-party-analytics-library"
import XmlToJsonAdapter from "./xml_adapter"

class Client {

    public function main(){
        const xml_stocks = stock_data_provider.get_all_stocks()
        const json_format = new XmlToJsonAdapter(xml_stocks)

        this.visualize(xml_stocks)
        analytics.analyze(json_format)
    }

    // rest of code

}

Our code is finally working, good job in implementing the adapter pattern.

A key to learning design patterns is to know when to use them.

Let's go over opportunities on when to use this pattern.

When to use this pattern?

  • Incompatible Interfaces — When you want to integrate two or more classes with incompatible interfaces, you can use the adapter pattern to make it work.
  • Code Reuse — Use the adapter pattern when you want to reuse legacy code without making any modifications to the original code.

Benefits

Throughout this article, we have covered the technical aspects of the adapter pattern.

Let's take a step back, and think about why is this pattern useful:

  • Single Responsibility Principle — You separate the data conversion code, from the underlying business logic.
  • Open Closed Principle — You can easily introduce new adapters, without breaking the existing client code, as long as they work with a common interface.

Downsides

Time to hit reality, it's not all sunshine and rainbows.

Let's go over the downsides in using the adapter pattern:

  • Complexity — The overall complexity of the code increases, due to the addition of extra classes and interfaces. Sometimes it's easier to change the code in the services. But keep in mind, the path of least resistance isn't always the best.
  • Adapter Chain — Sometimes many different adapters are required, causing an adapter chain. This kind of code simply gets very messy and hard to debug.

Conclusion

I would like to end it off with a question.

What do you think about this pattern?

Do you ever see yourself using it?

Can't wait to see your answers down in the comments.

This has been Tamerlan, I would appreciate it if you would follow me on Twitter @tamerlan_dev and check out my blog softwareadventuring.com for more content like this.

Today you learned:

  • Definition and implementation of the adapter pattern.
  • When to use the adapter pattern.
  • The benefits and downsides of using the adapter pattern.

Thanks for reading!

Further Readings

If you want to learn more about the design patterns, I would recommend Diving into Design Patterns. It explains all 23 design patterns found in the GoF book, in a fun and engaging manner.

Another book that I recommend is Heads First Design Patterns: A Brain-Friendly Guide, which has fun and easy-to-read explanations.

Top comments (1)

Collapse
 
zyabxwcd profile image
Akash

First of all, I like your name. Nice article. I was expecting it to be much more complicated.