The Adapter Pattern is a structural design pattern that allows two incompatible interfaces to work together by creating a wrapper around one of the interfaces to make it compatible with the other interface. In other words, it acts as a bridge between two different interfaces so that they can communicate and work together seamlessly.
As an example, we can think of a power adapter that you might use when traveling to another country. The power outlets in different countries have different shapes and sizes, but you can use an adapter to convert your device’s plug into the appropriate shape so that it can connect to the power outlet. The adapter acts as a wrapper around your device’s plug to make it compatible with the power outlet.
Similarly, the Adapter Pattern allows you to create a wrapper around one interface so that it can work with another interface that would otherwise be incompatible. This can be useful in situations where you have existing code that you want to reuse, but it doesn’t quite fit with the requirements of a new system or component. Instead of rewriting the code, you can create an adapter that makes it compatible with the new system or component.
Example using C Language
Let us try to understand the Adapter Pattern using an example for C language.
Let us say we have an existing code (i.e. an interface) called “LinkedList” that has methods ‘insert()’ and ‘delete()’ as shown below:
// Existing LinkedList interface
typedef struct node {
int val;
struct node* next;
} Node;
//Methods used for insert and delete to & from LinkedList
typedef struct {
void (*insert)(Node** head, int val);
void (*delete)(Node** head, int val);
} LinkedList;
A new requirement came to implement a new interface called “stack” which will have methods ‘push()’ & ‘pull()’. We can define a struct that will have two function pointers called push() & pull() as shown below:
// New Stack interface
typedef struct {
void (*push)(int val);
int (*pop)(void);
} Stack;
We have decided to re-use the “LinkedList” with this new “stack” interface. But they are not directly compatible. But we can make them compatible by using the adapter pattern. We will adapt the LinkedList to the stack and will call it “LinkedListStackAdaptor”. The implementation of this could be as below:
// Adapter class to make LinkedList objects work with Stack interface
typedef struct {
Stack stack;
Node** head;
} LinkedListStackAdapter;
// Implementation of push() method for LinkedListStackAdapter
static void pushToLinkedList(void* obj, int val) {
LinkedListStackAdapter* adapter = (LinkedListStackAdapter*)obj;
adapter->stack.push(val);
adapter->stack.pop(); // Removing the top element from stack as LinkedList only allows insertion at head
}
// Implementation of pop() method for LinkedListStackAdapter
static int popFromLinkedList(void* obj) {
LinkedListStackAdapter* adapter = (LinkedListStackAdapter*)obj;
int val = adapter->stack.pop();
adapter->stack.push(val); // Adding back the popped element to stack as LinkedList only allows deletion at head
return val;
}
// Constructor for LinkedListStackAdapter
LinkedListStackAdapter* createLinkedListStackAdapter(Node** head) {
LinkedListStackAdapter* adapter = malloc(sizeof(LinkedListStackAdapter));
adapter->stack.push = &pushToLinkedList;
adapter->stack.pop = &popFromLinkedList;
adapter->head = head;
return adapter;
}
This is quite impressing patterns as we have created a LinkedList object and used it with the Stack interface by creating an instance of the LinkedListStackAdapter:
int main() {
// Create a LinkedList object
Node* head = NULL;
LinkedList* list = malloc(sizeof(LinkedList));
list->insert = &insertToLinkedList;
list->delete = &deleteFromLinkedList;
// Use LinkedList with Stack interface using adapter
LinkedListStackAdapter* adapter = createLinkedListStackAdapter(&head);
adapter->stack.push(adapter, 10);
adapter->stack.push(adapter, 20);
printf("popped value: %d\n", adapter->stack.pop(adapter));
printf("popped value: %d\n", adapter->stack.pop(adapter));
free(list);
return 0;
}
So in this way, we have used the Adapter Pattern to create an adapter class (LinkedListStackAdapter) that makes an existing LinkedList object work with a new interface Stack. We have defined methods pushToLinkedList() and popFromLinkedList() that insert and delete values in the LinkedList by simulating the behavior of a stack. We have also demonstrated how the adapter can be used to push() and pop() values as if they were in a stack, but actually they are stored in a linked list.
Example using C++ Language
Let us say, we have an existing class Rectangle that has methods draw() and resize(). We have a new interface Shape that we want to use, which has methods display() and changeSize(). And we want to use the existing Rectangle objects with the new Shape interface, but they are incompatible. To make them work together, we can create an adapter class called RectangleShapeAdapter. So, we can create a Rectangle object and use it with the Shape interface by creating an instance of the RectangleShapeAdapter as shown in below C++ code.
class Rectangle {
public:
virtual void draw() const {
cout << "Drawing rectangle." << endl;
}
virtual void resize(int width, int height) {
cout << "Resizing rectangle to width: " << width << ", height: " << height << endl;
}
};
class Shape {
public:
virtual void display() const = 0;
virtual void changeSize(int width, int height) = 0;
};
class RectangleShapeAdapter : public Shape {
public:
RectangleShapeAdapter(Rectangle* rectangle) : rectangle_(rectangle) {}
virtual void display() const override {
rectangle_->draw();
}
virtual void changeSize(int width, int height) override {
rectangle_->resize(width, height);
}
private:
Rectangle* rectangle_;
};
int main() {
// Create a Rectangle object
Rectangle rectangle;
// Use Rectangle with Shape interface using adapter
RectangleShapeAdapter adapter(&rectangle);
adapter.display();
adapter.changeSize(100, 50);
return 0;
}
In this example, we have used the Adapter Pattern to create an adapter class (RectangleShapeAdapter) that makes an existing Rectangle object work with a new interface Shape. We have defined methods display() and changeSize() that call the corresponding methods of the Rectangle object. We have also demonstrated how the adapter can be used to display() and changeSize() the rectangle as if it were a shape, but actually it is a rectangle.
Advantages
- Reusability: The Adapter pattern promotes code reuse by allowing existing classes to be reused in new contexts.
- Flexibility: The Adapter pattern allows classes with incompatible interfaces to work together, making it easier to incorporate new components into an existing system.
- Maintainability: The Adapter pattern simplifies code maintenance by encapsulating the complexity of interfacing between incompatible classes.
- Easy to implement: The Adapter pattern is relatively easy to implement, and it does not require extensive modifications to existing code.
Disadvantages:
- Performance overhead: The use of Adapters can introduce a performance overhead, as it involves additional processing to convert between the different interfaces.
- Overuse: Overuse of the Adapter pattern can lead to excessive complexity and make the code harder to understand and maintain.
- Incomplete implementation: If the Adapter pattern is not implemented properly, it can lead to incomplete or incorrect results, which can be difficult to debug.
- Requires extra code: The Adapter pattern requires additional code to be written, which can increase the size and complexity of the system.
References
Related posts
- Design Patterns for Embedded Systems
- Builder Design Pattern
- Behavioral Pattern
- Factory & Abstract Factory Method Design Pattern