Software Design Pattern — Adapter Pattern

Zi Yang See
5 min readNov 15, 2020

--

Adapters

In this article, I will be sharing with you a very common software design pattern, the Adapter Pattern. First, I will be introducing what the Adapter Pattern is, before diving into some of its pros & cons, and also some examples.

Introduction

The Adapter Pattern is a software design pattern commonly used when we want to convert the interface of a class into another interface another client class expects. The ‘Adapter’ allows classes that could not originally work with each other due to incompatible interfaces to collaborate together.

The Adapter Pattern is a commonly used design pattern in many software programs, and is also one of the structural software design patterns described in the Gang of Four (GoF) Design Patterns book. This pattern is sometimes known as the Wrapper pattern too, as the adapter class wraps the interface of another incompatible class.

Participants

  • Target: The existing interface that clients communicate with.
  • Client: The class that communicates with the target.
  • Adaptee: The new incompatible interface that needs adapting.
  • Adapter: The class that adapts the Adaptee to the Target.

Implementation

The Adapter Pattern is commonly implemented in 2 main ways, the Class Adapter and the Object Adapter.

Class Adapter

In the Class Adapter implementation, inheritance is used. The Adapter inherits from both the Target and the Adaptee class. Thus, subtype polymorphism allows the Client to call the request method of the Adapter class which in turns calls the specificRequest method of the Adaptee. Note that the Class Adapter can only be implemented in languages that support multiple inheritance.

Object Adapter

In the Object Adapter implementation, composition is used instead. Similarly, the Client calls the request method of the Adapter class which in turns calls the specificRequest method of the Adaptee class it wraps over.

Use Cases, Pros & Cons

One of the most common use cases of the Adapter Pattern is when an incompatible module needs to be integrated with an existing module without making any source code modifications.

Another common usage is when we want to create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that don’t necessarily have compatible interfaces. Hence, should there be a change in adaptee in the future, there is no need to change the code in the client or target class.

Pros:

  • Data conversion logic is separated from business logic, thus adhering to the Single Responsibility Principle (SRP).
  • Since you can easily implement a new type of adapter without changing code in the client class, Open-Closed Principle (OCP) is fulfilled too.
  • Instead of modifying the client class to directly depend on the adaptee class, the Dependency Inversion Principle (DIP) is also fulfilled here.

Cons:

  • Can be overkill for small codebases as the use of any software design patterns increases the overall code complexity. Sometimes, for smaller codebases, it may be easier and more maintainable to directly alter the client/target or even the adaptee class’ interface.

Example

Round holes and square pegs example from Refactoring Guru

In this example from Refactoring Guru, our client class, RoundHole, needs to work with a RoundPeg target interface. However, we only a SquarePeg implementation. Thus, SquarePeg is our adaptee, while the SquarePegAdapter is the Adapter that allows RoundPeg to interface with SquarePeg. This is an Object Adapter implementation.

Now, when RoundHole wants to check if the peg can fit its hole radius, it will call the RoundPeg interface’s getRadius method. Since our SquarePegAdapter implements this interface, getRadius will then return the SquarePeg’s diagonal length from its centre to the corner. Thus, SquarePeg is ‘adapted’ to fit the RoundHole as long as its diagonal fits.

Actual Example

Now, you might be thinking that the above example is quite theoretical and would be wondering, how can I use this design pattern in my day to day apps that have a Client-Server architecture?

In a software project I worked on called DrawBerry, I have used the Adapter Pattern to allow my client to interface with my backend.

DrawBerry is a multiplayer drawing game on iOS that was built with Swift and also uses a Firebase backend. To briefly describe the game, users can create profiles, join game rooms, and play drawing games with their friends.

Network Components of DrawBerry

The above diagram does not show the Client classes, which are the various Model components (such as Room and Game) and the Adaptees, which are just the Firebase APIs for Swift. Note that in Swift, Protocols are similar to what Interfaces are in languages like Java.

To further illustrate, for example, in our classic game mode, the Model has to sync up game states between multiple players over the network. Thus, the ClassicGame model contains a GameNetwork object. Since we are using Firebase as our backend, the FirebaseNetworkAdapters contains the logic to send and receive data to and from our backend. The Model does not have to directly depend on Firebase’s API, but instead on the GameNetwork protocol.

There are many pros to using the Adapter Pattern here, including:

  • Reduced coupling and increased cohesion.
  • The ViewControllers and Models, including the game logic, can be developed independently from the networking logic.
  • Simplified interface for our Models to update game state over the network (more described below).
  • If we were to change our backend service provider, such as to AWS or our own servers, the code in the Model components does not have to be changed.

In the following code example, we can see how DrawBerry’s classic game mode sync up each round’s topic between all players.

The target interface, GameNetwork.
Our client class, the ClassicGame model.
Our adapter, FirebaseGameNetworkAdapter.

As seen, from the code example, the ClassicGame model just has to depend on the GameNetwork protocol (interface). It does not have to worry about how it has to interface with Firebase’s much more complex APIs.

You can refer to the code implementation of DrawBerry here.

References

Image Sources

--

--