Adapter pattern. Review and research. Examples of implementation in C++
Contents
- 1. Assigning a pattern Adapter
- 2. When is the Adapter pattern used? Cases of using the Adapter pattern
- 3. The structure of pattern Adapter (picture)
- 4. An example of implementation of the Adapter pattern for a class in C++
- 5. An example of implementation of the Adapter pattern for an object in C++
- 6. The results of the Adapter pattern for the class. Examples in C++
- 7. Results of the Adapter pattern for an object. Examples
- Related topics
Search other websites:
1. Assigning a pattern Adapter
The Adapter pattern belongs to structural patterns and is used to structure classes and objects. The need to use the Adapter pattern arises in cases when it is necessary to bring (adapt) one system to the requirements of another system.
The Adapter pattern can perform not only simple transformations (for example, changing names), but also implementing a completely different set of operations (substituting one code for another).
⇑
2. When is the Adapter pattern used? Cases of using the Adapter pattern
The Adapter pattern is used in the following cases:
- if a certain class needs to be included in the developed program, but the interface of this class does not meet the requirements of the program;
- if the program needs to create a class that is reused. In this case, the class must interact with other (unknown) classes that have interfaces incompatible with the program;
- if you need to use multiple existing subclasses in one class. This is the case when an object adapter is used to accommodate these subclasses to a common parent class.
⇑
3. The structure of pattern Adapter (picture)
There are two types of adapter pattern
- for the class (Figure 1). In this case, multiple inheritance is applied to adapt one interface to another;
- for the object (Figure 2). In this case, object composition is applied.
The structures shown in Figure 1 and Figure 2 schematically show the implementation features of the Adapter pattern. The Adaptee class is a class that contains a SpecificRequest() method that needs to be adapted to the needs of the Client, which uses the Request() method. The Request() method is declared in the Target interface, which is referenced by the Client. A binder Adapter class is created between the Target interface and the Adaptee class and performs the necessary conversions.
The Client uses the Target interface to access the methods implemented in the Adapter class. The adapter, in turn, calls methods of the Adaptee class.
There are two ways to implement the Adapter class:
- method of inheritance (Figure 1). This method is called the adapter for the class. In this way, the Adapter class inherits the methods of the Adaptee class and implements the methods of the Target interface;
- method of objects composition. This method is called an adapter for an object. With this method, the Adapter class uses a reference (instance, object) of type Adaptee.
Figure 1. The structure of Adapter for class
Figure 2. The structure of Adapter for object
⇑
4. An example of implementation of the Adapter pattern for a class in C++
The example implements an adapter for a class in C++, which corresponds to Figure 1 of this topic.
#include <iostream> using namespace std; // Adapter pattern for class. // Adapts the Adaptee class to the Target class through the Adapter intermediate class. // Abstract class Target class Target { public: virtual void Request() = 0; }; // The class whose method needs to be adapted to another system. // In this case, adapting the name of the SpecificRequest () method to the Request () method class Adaptee { public: // Some specific method void SpecificRequest() { cout << "Adaptee.SpecificRequest()" << endl; } }; // Class Adapter - uses multiple inheritance class Adapter :public Target, public Adaptee { public: void Request() { // Invokes SpecificRequest() from class Adaptee SpecificRequest(); } }; // Class Client - gets a reference (or pointer) to Target class Client { public: // gets a reference to Target void ClientMethod(Target& target) { target.Request(); } // gets a pointer to Target void ClientMethod(Target* target) { target->Request(); } }; void main() { // Demonstration of the Adapter pattern for the class // you need to adapt the SpecificRequest() method of the Adaptee class to the needs of the client instance Client client; Target* target = (Target*) new Adapter(); // pass an instance of adaptee to the client via the target interface client.ClientMethod(target); // Adaptee.SpecificRequest() }
⇑
5. An example of implementation of the Adapter pattern for an object in C++
In the example, in C ++, the Adapter pattern is implemented for an object that corresponds to Figure 2 of this topic.
#include <iostream> using namespace std; // The Adapter pattern for an object. // Abstract class Target class Target { public: virtual void Request() = 0; }; // The class in which the SpecificRequest() method needs to be passed to the client as the Request() method class Adaptee { public: // Some specific method void SpecificRequest() { cout << "Adaptee.SpecificRequest()" << endl; } }; // Adapter class - uses objects composition class Adapter :public Target { private: Adaptee* adaptee; public: // Constructor - receives a pointer to adaptee Adapter(Adaptee* _adaptee) :adaptee(_adaptee) {} void Request() { // Invokes SpecificRequest() from class Adaptee adaptee->SpecificRequest(); } }; // Class Client - receives a reference (or pointer) to Target class Client { public: // receives a reference to Target void ClientMethod(Target& target) { target.Request(); } // receives a pointer to Target void ClientMethod(Target* target) { target->Request(); } }; void main() { // Demonstration of pattern Adapter for object // Instance of the Client class, which can receive the code of the SpecificRequest() method of the Adaptee class Client client; // An instance containing the SpecificRequest() method Adaptee adaptee; // Reference to the base class that will be passed to the client Target* target; // converting adaptee->target through Adapter binder class target = new Adapter(&adaptee); // Pass target to the client client.ClientMethod(target); }
⇑
6. The results of the Adapter pattern for the class. Examples in C++
If the Adapter pattern is applied to a class (Figure 1), the following results can be obtained.
- If you need to adapt a class and its subclasses at the same time, the pattern will not work. Since, when inheriting, actions are delegated to a specific (only one) Adaptee class (see example 1).
- It is possible to replace some operations of the adapted Adaptee class with others. This is due to the fact that the Adapter class is a subclass of the Adaptee class (inheritance is used, see example 2).
- The pattern introduces only one new object (see example 3).
The following examples demonstrate all of the listed results of the Adapter pattern for a class.
Example 1. The example demonstrates the impossibility of implementing the Adapter pattern for a class if the classes form a hierarchy.
... // Adapter for class. // The pattern does not work if you simultaneously adapt several classes that form a hierarchy. // Class A - base class for class B class A { public: virtual void SpecifiedRequest() { cout << "A.SpecifiedRequest()" << endl; } }; // Class B - inherited from A class B :A { public: virtual void SpecifiedRequest() { cout << "B.SpecifiedRequest()" << endl; } }; // Class adapter - inherits classes A, B class Adapter : private A, private B, public ITarget { public: void Request() { // Here the error is an ambiguous call (Adapter::SpecifiedRequest is ambiguous) // it is not clear what class the SpecifiedRequest() method needs to be called. SpecifiedRequest(); } }; void main(void) { }
As you can see from the example, two classes A, B form a hierarchy. Class A is a base class, class B is a derived class. The Adapter class attempts to adapt the SpecifiedRequest() method of the A, B classes into the Request() method. However, with such a call
... void Request() { // Here the error is an ambiguous call (Adapter::SpecifiedRequest is ambiguous) // it is not clear what class the SpecifiedRequest() method needs to be called. SpecifiedRequest(); } ...
the compiler will generate an error
Adapter::SpecifiedRequest is ambiguous
which means an ambiguous challenge. Here you need to choose one of two calls (classes)
A::SpecifiedRequest();
or
B::SpecifiedRequest();
If you write in the Request() method
A::SpecifiedRequest(); B::SpecifiedRequest();
then it will call two methods instead of one, which also does not fit the ideology of the pattern.
To implement the ability to work simultaneously with many adapted objects, the adapter for a class must be replaced with an adapter for an object (see below).
Example 2. The example demonstrates a code snippet in which a completely different replacement code is entered in the Request() method of the Adapter class. Since, when inheriting, the Adapter class is a subclass of the Adaptee class, this allows you to completely replace the operations of the adapted Adaptee class.
// Adapter class - inherits classes Adaptee, ITarget class Adapter : private Adaptee, public ITarget { public: void Request() { // SpecifiedRequest(); - the method of the Adaptee class will not be called, // another code fits in instead cout << "Another program code" << endl; } };
Example 3. The example demonstrates a client code snippet that shows that you do not need to create an object of the Adaptee class that adapts. This is due to the fact that all necessary replacement operations are implemented through the inheritance mechanism in the Adapter class.
... void main() { // Demonstration of the Adapter pattern for the class. // It is necessary to adapt the method SpecificRequest() of Adaptee class instance to client needs Client client; Target* target = (Target*) new Adapter(); // pass an instance of target to the client client.ClientMethod(target); }
In the above code snippet, the main() function is the client code. In main(), you don’t need to instantiate this class to get to the Adaptee class.
⇑
7. Results of the Adapter pattern for an object. Examples
If the Adapter pattern is applied to an object (see Figure 2), you can get the following results.
- The Adapter class can work simultaneously with many objects that need to be adapted. This also applies when the classes to be adapted form a hierarchy (see example 1 below).
- If the classes to be adapted form a hierarchy, the object adapter can add new (or change) functionality to all the objects that are being adapted.
- Difficulty in replacing operations (methods) of the Adaptee class, which is adaptable. To override a method (operation) in the Adapter class, you must inherit a subclass from Adaptee, and then use a reference to that subclass, not Adaptee (see example 2 below).
Example 1. The example demonstrates the possibility of simultaneous use of classes that form a hierarchy. Let there be given two classes that form a hierarchy: base class A and derived class B.
// Adapter for object. // The pattern allows you to simultaneously adapt several classes that form a hierarchy. #include <iostream> using namespace std; class ITarget { public: virtual void Request() = 0; }; // Class A - base class class A { public: virtual void SpecifiedRequest() { cout << "A.SpecifiedRequest()" << endl; } }; // Class B - derived from class A class B: public A { public: virtual void SpecifiedRequest() { cout << "B.SpecifiedRequest()" << endl; } }; // Class Adapter - uses object composition, // inherits the ITarget class class Adapter : public ITarget { private: A* adaptee; public: // Constructor, receives a reference to the top of the hierarchy, // the client can use this reference to pass any class in the hierarchy. // This is an advantage of the Adapter for objects pattern over the Adapter for classes. Adapter(A* adaptee) { this->adaptee = adaptee; } virtual void Request() { // Invokes method SpecifiedRequest() adaptee->SpecifiedRequest(); } }; // Class Client - uses adapter class Client { public: // Client method - will call methods of different classes from the hierarchy void ClientMethod(ITarget* target) { target->Request(); } }; void main(void) { // 1. Pass the SpecificRequest() method of class A to the client Client client; // Declare an instance of the Client class // Declare an instance of class A A objA; Adapter adapterA(&objA); // Call the client method - pass an instance of the class A client.ClientMethod((ITarget*)&adapterA); // A.SpecificRequest() // 2. Pass the SpecificRequest() method of class B to the client B objB; Adapter adapterB((A*)&objB); client.ClientMethod((ITarget*)&adapterB); // B.SpecificRequest() // 3. Another client use case ITarget* client2; // pointer to interface ITarget client2 = (ITarget*)&adapterA; client2->Request(); // A.SpecificRequest() client2 = (ITarget*)&adapterB; client2->Request(); // B.SpecificRequest }
Example 2. The example demonstrates overriding an operation in a class to be adapted for the Adapter pattern for objects. To replace the operation (method) SpecificRequest() in the Adaptee class, do the following:
- the AdapteeSub class inherits from the Adaptee class;
- the AdapteeSub class declares the SpecificRequest() method, which overrides the SpecificRequest() method of the Adaptee class;
- in the Adapter class, a pointer (reference) to the derived AdapteeSub class is declared;
- in the Adapter class, in the Request() method, the SpecificRequest() method of the AdapteeSub class is called.
// Object adapter. // Demonstration of method replacement in the Adaptee class // in the case of implementing an adapter for an object. #include <iostream> using namespace std; class ITarget { public: virtual void Request() = 0; }; // Class Adaptee - which need to be adapted class Adaptee { public: void SpecifiedRequest() { cout << "Adaptee.SpecifiedRequest()" << endl; } }; // Subclass of class Adaptee class AdapteeSub : private Adaptee { public: void SpecifiedRequest() { cout << "Another operation: AdapteeSub.SpecifiedRequest()"; } }; // Adapter class - uses objects composition, // inherits from the ITarget class class Adapter : public ITarget { private: AdapteeSub* adapteeSub; // a reference to a subclass public: // Constructor, gets a reference to the base Adaptee class Adapter(Adaptee* adaptee) { this->adapteeSub = (AdapteeSub*)adaptee; } virtual void Request() { // Invokes method SpecifiedRequest() adapteeSub->SpecifiedRequest(); } }; // Client class - uses an adapter class Client { public: // Client method - calls methods of different classes from the ITarget hierarchy void ClientMethod(ITarget* target) { target->Request(); } }; void main(void) { // 1. Pass the SpecificRequest() method of class A to the client Client client; // Declare an instance of the Client class // Declare an instance of the AdapteeSub class that implements // the override for the Adaptee :: SpecifiedRequest () operation AdapteeSub obj; Adapter adapter((Adaptee*)&obj); // Pass an obj instance to the adapter // Pass adapter to client client.ClientMethod((ITarget*)&adapter); // Another operation: AdapteeSub.SpecifiedRequest() }
⇑
Related topics
- Java. Examples of implementation of pattern Adapter
- C#. Examples of implementation of the Adapter pattern in C#
- Solving a problem using the Adapter pattern in Java and C#. Laboratory work. Adaptation of three methods
⇑