Structural Design Patterns #01 - Adapter

In this series, I am going to have a look at the most-known Design Patterns. Instead of just giving examples, I also want to focus on the problems these patterns solve. In the first post, I am going to talk about the Adapter pattern.

The Problem

Let's say we are working in the quality assurance department of a company that produces parts for an automotive company. At the end of the production line a program uses a camera to make a picture of the finished part. An AI can then decide if the part achieved our quality standards.

Currently, the company is using very old cameras to make photos. To access the camera we use a third-party library that is provided by the manufacturer. When the program was developed the developers did not think about future changes in camera equipment. Our application therefore just makes direct function calls to the third-party library:

[...]
oldCamera = new ThridPartyCamera([...]);
oldCamera.MakePhoto(800, 1, 0.3);
[...]

Let's say we now want to switch our cameras to newer cameras from a different manufacturer. This manufacturer also delivers a new library to interact with the cameras. Now there are two scenarios:

  1. The new library fits our current implementation and we could replace oldCamera.MakePhoto(800, 1, 0.3); with newCamera.TakePhoto(800, 1, 0.3); (the new library would need a method with the same signature that exactly provides the same functionality as the old library)

  2. We are not in luck. The new library does not fit the old library

Unfortunately in our example scenario, we are not in luck, because we have to cover the adapter pattern in this post. So the method of the new camera in the new library will look like this:

class NewCamera
{
    byte[] MakePhotoWithHighTechSettings(string specialIdentifier) 
    {
        [...]
    }
}

Decouple implementation details from buisness logic

Nevertheless, no matter which scenario, a weakness of the current structure is that the business logic is coupled with the implementation details of the camera. No matter which scenario, theoretically we could change every function call in our application to fit the new library. But if we are maintaining a big project this would be an error-prone and tedious approach. Additionally, if we need to change the camera again at a later stage we would need to do the same thing again.

To separate these different concerns we can create an ICamera interface that can then be used by our business logic:

interface ICamera 
{
    byte[] MakePhoto(int iso, int mode, double shutterSpeed);
}

class Camera : ICamera
{   
    ThridPartyCamera oldCamera;
    Camera()
    {
        oldCamera = new ThridPartyCamera([...]);
    }

    byte[] MakePhoto(int iso, int mode, double shutterSpeed)
    {
        oldCamera.MakePhoto(iso, mode, shutterSpeed);
    }
}
//inside business logic:
[...]
ICamera camera = new Camera();
camera.MakePhoto(800, 1, 0.3);
[...]

This is an advantage. If we now want to change to a new camera we can just create a class that implements the ICamera interface and only need to change the initialization.

The adapter

Even if the software interface of the new camera does not fit our ICamera interface, like in our example, we can just add code to the implementation that adapts the new interface to the ICamera interface. We can call this class an Adapter class.

class NewCameraAdapter : ICamera
{
    NewCamera newCamera;

    NewCameraAdapter()
    {
        newCamera = new NewCamera([...]);
    }

    byte[] MakePhoto(int iso, int mode, double shutterSpeed) 
    {
        string specialIdentifier = createSpecialIdentifier([...]);
        return newCamera.MakePhotoWithHighTechSettings(specialIdentifier);
    }

    void createSpecialIdentifier([...])
    {
        [...]
    }
}

The Adapter can then be used inside our business logic just like the old camera before:

//inside business logic:
ICamera camera = new NewCameraAdapter();
camera.MakePhoto(800, 1, 0.2);
[...]

Additionally, if we want to support multiple cameras in the future we can create multiple implementations of the ICamera interface.

 //inside business logic:
ICamera camera = new NewCameraAdapter();
camera.MakePhoto(800, 1, 0.2);
[...]

ICamera cameraTwo = new SuperHighTechCameraAdapter();
camera.MakePhoto(800, 3, 0.1);
[...]

In this structure, the method calls inside the business logic don't need to change when a new camera is used in our application. This can be a major advantage. If the implementation of the ICamera interface adapts to another interface we call this structure the adapter pattern.

Adapter pattern in UML

To end the post, let's map the entities from the UML diagram to the entities in our camera example:

  • Client Interface: ICamera

  • Adapter: NewCameraAdapter

  • Service: NewCamera

  • Client: business logic

That's it for this post, I hope you could learn something! Additionally, if you want to get informed about future posts: sUbsCr1be t0 mY n3wslet1er!1