Professional C__ - Marc Gregoire [490]
Example: Adapting a Logger Class
For this adapter pattern example, let’s assume a very basic Logger class. Methods are shown with their implementations directly in the header file. This is not Best Practice, but is done to save space.
#include #include class Logger { public: static const std::string kLogLevelDebug; static const std::string kLogLevelInfo; static const std::string kLogLevelError; Logger() { std::cout << "Logger constructor" << std::endl; } void log(const std::string& level, const std::string& str) { std::cout << level << ": " << str << std::endl; } }; const std::string Logger::kLogLevelDebug = "DEBUG"; const std::string Logger::kLogLevelInfo = "INFO"; const std::string Logger::kLogLevelError = "ERROR"; Code snippet from LoggerAdapter\LoggerAdapter.h The Logger class has a constructor, which outputs a line of text to the standard console, and a method called log() that writes the given line of text to the console prefixed with a log level. One reason why you might want to write a wrapper class around this basic Logger class is to change the interface of it. Maybe you are not interested in the log level and you would like to call the log() method with only one parameter, the message itself. Implementation of an Adapter The first step in implementing the adapter pattern is to define the new interface for the underlying functionality. This new interface is called NewLoggerInterface and looks as follows: class NewLoggerInterface { public: virtual void log(const std::string& str) = 0; }; Code snippet from LoggerAdapter\LoggerAdapter.h This class is an abstract class, which declares the desired interface that you want for your new logger. It only defines one abstract method which needs to be implemented by any class inheriting from this interface. You can think of NewLoggerInterface as a mix-in class. Mix-in classes are discussed in Chapter 28. The next step is to write the actual new logger class, NewLoggerAdapter, which inherits from the NewLoggerInterface so that it has the interface that you designed. It also privately inherits from the original Logger class. It is inherited privately so that no functionality from the original Logger class will be publicly available in the NewLoggerAdapter class. The constructor of the new class writes a line to standard output to keep track of which constructors are being called. The code then implements the abstract log() method from the NewLoggerInterface interface by forwarding the call to the original log() method and specifying kLogLevelInfo as log level: class NewLoggerAdapter : public NewLoggerInterface, private Logger { public: NewLoggerAdapter() { std::cout << "NewLoggerAdapter constructor" << std::endl; } virtual void log(const std::string& str) { Logger::log(Logger::kLogLevelInfo, str); } }; Code snippet from LoggerAdapter\LoggerAdapter.h Using an Adapter Since adapters exist to provide a more appropriate interface for the underlying functionality, their use should be straightforward and specific to the particular case. Given the previous example, the following program uses the new simplified interface for the Logger class: #include "LoggerAdapter.h" int main() { NewLoggerAdapter logger; logger.log("Testing the logger."); return 0; } Code snippet from LoggerAdapter\TestLoggerAdapter.cpp When you compile and run this example, it will produce the following output: Logger constructor NewLoggerAdapter constructor INFO: Testing the logger. THE DECORATOR PATTERN
The decorator pattern is exactly what it sounds like — a “decoration” on an object. The pattern is used to change the behavior of an object at run time. Decorators are a lot like subclasses, but their effects can be temporary. For example, if you have a stream of data that you are parsing and you reach data that represents