The Adapter Pattern
This post is a cross-post from www.ModernesCpp.com.
The idea of the adapter pattern is straightforward: It converts the interface of a class into another interface.
Imagine you have a class that implements the functionality required by the client. There is only one issue: the interface does not follow your company's naming policy. Thanks to the Adapter Pattern, you can pretty easily support the required interface using the existing class.
The Adapter Pattern is the only pattern inside the book "Design Patterns: Elements of Reusable Object-Oriented Software"?(Design Pattern) that is implemented on a class level but also on an object level. Here are the facts before I show you both ways to implement this structural pattern.
The Adapter Pattern
Purpose
Also known as
Use Case
Example
Container Adapter
The container adapters std::stack, std::queue, and std::priority_queue provide a different interface for the sequence containers. The following code snippet shows the template signature of the three container adapters:
template<typename T, typename Container = std::deque<T>>
class stack;
template<typename T, typename Container = std::deque<T>>
class queue;
template<typename T, typename Container = std::vector<T>,
typename Compare = std::less<typename Container::value_type>>
class priority_queue;
By default, std::stack, and std::queue use std::deque as sequence container, but std::priority_queue uses std::vector. Additionally, std::priority_queue also requires a comparator that is defaulted to std::less of the sequence container elements.
?C++ has more adapters.
Iterator Adapter
C++ supports insert iterators and streams iterators.
With the three insert iterators std::front_inserter, std::back_inserter, and std::inserter, you can insert an element into a container at the beginning, at the end or an arbitrary position, respectively.
Stream iterator adapters can use streams as a data source or data sink. C++ offers two functions to create istream iterators and two to create ostream iterators. The created istream iterators behave like input iterators, the ostream iterators like insert iterators.
Structure
The following two class diagrams show the structure of the Adapter Pattern, based on classes or on objects. For short, I call them class adapters and object adapters.
Class Adapter
Object Adapter
Client
Adaptor
Adaptee
领英推荐
Now, the class or object-based Adapter Pattern should be straightforward.
Modernes C++ Mentoring
Get the invitation to the one-hour presentation of my mentoring program "Fundamentals for C++ Professionals" including Q&A
Do you want the invitation to the Zoom meeting?
???Write an e-mail with the subject 2022-10-10 or 2022-10-31 to [email protected].
Implementation
Class Adapter
In the following example, the class RectangleAdapter adapts the interface of the LegacyRectangle.
// adapterClass.cpp
#include <iostream>
typedef int Coordinate;
typedef int Dimension;
class Rectangle {
public:
virtual void draw() = 0;
virtual ~Rectangle() = default;
};
class LegacyRectangle {
public:
LegacyRectangle(Coordinate x1, Coordinate y1, Coordinate x2, Coordinate y2) : x1_(x1), y1_(y1), x2_(x2), y2_(y2){
std::cout << "LegacyRectangle: create. (" << x1_ << "," << y1_ << ") => ("
<< x2_ << "," << y2_ << ")" << '\n';
}
void oldDraw() {
std::cout << "LegacyRectangle: oldDraw. (" << x1_ << "," << y1_
<< ") => (" << x2_ << "," << y2_ << ")" << '\n';
}
private:
Coordinate x1_;
Coordinate y1_;
Coordinate x2_;
Coordinate y2_;
};
class RectangleAdapter : public Rectangle, private LegacyRectangle {
public:
RectangleAdapter(Coordinate x, Coordinate y, Dimension w, Dimension h) : LegacyRectangle(x, y, x + w, y + h) { // (1)
std::cout << "RectangleAdapter: create. (" << x << "," << y
<< "), width = " << w << ", height = " << h << '\n';
}
void draw() override {
oldDraw();
std::cout << "RectangleAdapter: draw." << '\n';
}
};
int main() {
std::cout << '\n';
Rectangle* r = new RectangleAdapter(120, 200, 60, 40);
r->draw();
delete r;
std::cout << '\n';
}
RectangleAdapter derived the interface from the Rectangle, and the implementation for LegacyRectangle using multiple inheritances. Additionally, RectangleAdapter adapts the size of the LegacyRectangle (line 1).
This implementation of the Adapter Pattern is one of the rare use cases for private inheritance. Let me write a few words about interface inheritance and implementation inheritance.
Finally, here is the output of the program:
Object Adapter
In the following implementation, the RectangleAdapter delegates its calls to its adaptee LegacyRectangle.
// adapterObject.cpp
#include <iostream>
typedef int Coordinate;
typedef int Dimension;
class LegacyRectangle {
public:
LegacyRectangle(Coordinate x1, Coordinate y1, Coordinate x2, Coordinate y2) : x1_(x1), y1_(y1), x2_(x2), y2_(y2){
std::cout << "LegacyRectangle: create. (" << x1_ << "," << y1_ << ") => ("
<< x2_ << "," << y2_ << ")" << '\n';
}
void oldDraw() {
std::cout << "LegacyRectangle: oldDraw. (" << x1_ << "," << y1_
<< ") => (" << x2_ << "," << y2_ << ")" << '\n';
}
private:
Coordinate x1_;
Coordinate y1_;
Coordinate x2_;
Coordinate y2_;
};
class RectangleAdapter {
public:
RectangleAdapter(Coordinate x, Coordinate y, Dimension w, Dimension h) : legacyRectangle{LegacyRectangle(x, y, x + w, y + h)} { // (1)
std::cout << "RectangleAdapter: create. (" << x << "," << y
<< "), width = " << w << ", height = " << h << '\n';
}
void draw() {
legacyRectangle.oldDraw();
std::cout << "RectangleAdapter: draw." << '\n';
}
private:
LegacyRectangle legacyRectangle;
};
int main() {
std::cout << '\n';
RectangleAdapter r(120, 200, 60, 40);
r.draw();
std::cout << '\n';
}
?
The class RectangleAdapter creates it LegacyRectangle directly in its constructor (line 1). Another option would be to make LegacyRectangle? a constructor parameter of RectangleAdapter:
class RectangleAdapter {
public:
RectangleAdapter(const LegacyRectangle& legRec): legacyRectangle{legRec} {}
...
};
The output of this program is identical to the previous one.
Related Patterns
You may ask yourself: Should I use a class adapter or an object adapter.
Class Adapter versus Object Adapter
Class Adapter
The class adapter applies classes and their subclasses. It uses the separation of interface and implementation and runtime dispatch with virtual function calls. Its functionality is hard-coded and available at compile time. The class adapter provides less flexibility and dynamic behavior, such as the object adapter.
Object Adapter
The object adapter uses the relationship of objects.
You build your abstraction by composing objects and delegating their work. This composition can be done at runtime. Consequentially, an object adapter is more flexible and allows it to exchange the delegated object at run time.
What's Next?
The Bridge Pattern helps to separate the interface from its implementation. Let me introduce it in my next post.
Software Architect with 16+ years of experience in C++ 11/14/17. Proven expertise in software design, development, and maintenance. Passionate about Mathematics, Algorithms, Chess, and problem-solving.
2 年It's equally important to understand the subtle but very important differences between 'Adapter','Decorator' and the 'Facade'.