The Adapter Pattern
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
- Translate one interface into another one
Also known as
- Wrapper
Use Case
- A class does not have the required interface
- Definition of a general interface for a set of similar classes
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.
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
- Insert 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 Iterators
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
- Uses the member function
methodA()
of theAdapter
Adaptor
- Class
- Provides the functionality of
methodA()
using multiple inheritances - Is publicly derived from
Interface
and privately fromImplementation
- Provides the functionality of
- Object
- Delegates the member function call to its member
Adaptee
- Delegates the member function call to its member
Adaptee
- Implements the functionality of the client
Now, the class or object based Adapter Pattern should be straightforward.
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.
- Interface inheritance uses public inheritance. It separates users from implementations to allow derived classes to be added and changed without affecting the users of the base class. Derived classes support the interface of the base class.
- Implementation inheritance often uses private inheritance. Typically, the derived class provides its functionality by adapting functionality from the base class. Derived classes don’t support the interface of the base class.
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
- The Bridge Pattern is similar to the object adapter but has a different intent. The Bridge Pattern’s purpose is to separate the interface from the implementation, but the adapter’s purpose is to modify an existing interface.
- The Decorator Pattern extends an object without changing its interface. Decorators are pluggable but not bridges or adapters.
- The Proxy Pattern extends the implementation for the object it stands for but doesn’t change its interface.
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.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!