Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 26

The Singleton Pattern

The singleton is one of the simplest design patterns. In English the word “singleton” means “one of a kind” or “individual.” It has a similar meaning in programming. The singleton pattern is a strategy for enforcing the existence of exactly one instance of a class in a program. Applying the singleton pattern to a class guarantees that only one object of that class will ever be created. The singleton pattern also specifies that the one object is globally accessible from anywhere in the program. Programmers usually refer to a class following the singleton pattern as a singleton class.

The singleton pattern is especially helpful when you design a program with a general application class that handles the startup, shutdown, and flow control of the application. It would be inappropriate to have two application objects in a single program. In fact, it could prove disastrous to have two application objects that both think they are controlling the flow of the application. By using the singleton pattern, you can ensure that there is exactly one application object accessible from anywhere in the program.

You should use the singleton pattern whenever you want to create exactly one object of a class in a program. If your program relies on the assumption that there will be exactly one instance of a class, you should enforce that assumption with the singleton pattern.

Example: A Logging Mechanism

Singletons are particularly useful for utility classes. Many applications have a notion of a logger — a class that is responsible for writing status information, debugging data, and errors to a central location. The ideal logging class has the following characteristics:

It is available at all times.

It is easy to use.

It provides a set of useful features.

The singleton pattern is a good match for a logger because, even though the logger could be used in many different contexts and for many different purposes, it is conceptually a single instance. Implementing the logger class as a singleton also makes it easier to use because you never have to worry about which logger is the current one or how to get a hold of the current logger. Because there’s only one, it’s a moot point!

Implementation of a Singleton

There are two basic ways to implement a singleton in C++. The first approach uses static methods to form a class that needs no instantiation. The second uses access control levels to regulate the creation and access of one single instance.

Both approaches are shown here, using a simple Logger class as an example. This Logger class provides the following features:

It can log a single string or a vector of strings.

Each log message has an associated log level, which is prefixed to the log message.

Every log message is flushed to disk so that it will appear in the file immediately.

754

Applying Design Patterns

Static Class Singleton

Technically, a class that uses all static methods isn’t really a singleton: it’s a nothington, to coin a new term. The term singleton implies that there is exactly one instance of the class. If all of the methods are static and the class is never instantiated at all, can you call it a singleton? The authors claim that, because design patterns exist to help you build a mental model of object-oriented structures, you can call a static class a singleton if you please. However, you should recognize that a static class as a singleton lacks polymorphism and a built-in mechanism for construction and destruction. For cases like the Logger class, these may be acceptable losses.

The public interface to the Logger static class follows. Note that it uses all static methods for access, so there is no need ever to instantiate a Logger object. In fact, the constructor has been made private to enforce this behavior.

/**

*Logger.h

*Definition of a singleton logger class, implemented with static methods

*/

#include <iostream> #include <fstream> #include <vector> class Logger

{

public:

static const std::string kLogLevelDebug; static const std::string kLogLevelInfo; static const std::string kLogLevelError;

// Logs a single message at the given log level static void log(const std::string& inMessage,

const std::string& inLogLevel);

// Logs a vector of messages at the given log level

static void log(const std::vector<std::string>& inMessages, const std::string& inLogLevel);

// Closes the log file static void teardown();

protected:

static void init();

static const char* const kLogFileName;

static bool sInitialized;

static std::ofstream sOutputStream;

private: Logger() {}

};

755

Chapter 26

The implementation of the Logger class is fairly straightforward. The sInitialized static member is checked within each logging call to make sure that the init() method has been called to open the log file. Once the log file has been opened, each log message is written to it with the log level prepended.

/**

*Logger.cpp

*Implementation of a singleton logger class

*/

#include <string> #include “Logger.h”

using namespace std;

const string Logger::kLogLevelDebug = “DEBUG”; const string Logger::kLogLevelInfo = “INFO”; const string Logger::kLogLevelError = “ERROR”;

const char* const Logger::kLogFileName = “log.out”;

bool Logger::sInitialized = false; ofstream Logger::sOutputStream;

void Logger::log(const string& inMessage, const string& inLogLevel)

{

if (!sInitialized) { init();

}

// Print the message and flush the stream with endl. sOutputStream << inLogLevel << “: “ << inMessage << endl;

}

void Logger::log(const vector<string>& inMessages, const string& inLogLevel)

{

for (size_t i = 0; i < inMessages.size(); i++) {

log(inMessages[i], inLogLevel);

}

}

void Logger::teardown()

{

if (sInitialized) { sOutputStream.close(); sInitialized = false;

}

}

void Logger::init()

{

if (!sInitialized) { sOutputStream.open(kLogFileName, ios_base::app); if (!sOutputStream.good()) {

756

Applying Design Patterns

cerr << “Unable to initialize the Logger!” << endl; return;

}

sInitialized = true;

}

}

Access-Controlled Singleton

Object-oriented purists (Warning: they are out there, and they may work at your company!) might scoff at the static class solution to the singleton problem. Since you can’t instantiate a Logger object, you can’t build a hierarchy of loggers and make use of polymorphism. Such a hierarchy is rarely employed in the singleton case, but it is a valid drawback. Perhaps more significantly, as a result of using entirely static methods, there is no object orientation at all. The class built in the previous example is essentially a collection of C-style functions, not a cohesive class.

To build a true singleton in C++, you can use the access control mechanisms as well as the static keyword. With this approach, an actual Logger object exists at run time, and the class enforces that exactly one exists. Clients can always get a hold of that object through a static method called instance(). The class definition looks like this:

/**

*Logger.h

*Definition of a true singleton logger class

*/

#include <iostream> #include <fstream> #include <vector>

class Logger

{

public:

static const std::string kLogLevelDebug; static const std::string kLogLevelInfo; static const std::string kLogLevelError;

//Returns a reference to the singleton Logger object static Logger& instance();

//Logs a single message at the given log level

void log(const std::string& inMessage, const std::string& inLogLevel);

// Logs a vector of messages at the given log level void log(const std::vector<std::string>& inMessages,

const std::string& inLogLevel);

protected:

//Static variable for the one-and-only instance static Logger sInstance;

//Constant for the filename

757

Chapter 26

static const char* const kLogFileName;

// Data member for the output stream std::ofstream mOutputStream;

private:

Logger();

~Logger();

};

One advantage of this approach is already apparent. Because an actual object will exist, the init() and teardown() methods present in the static solution can be omitted in favor of a constructor and destructor. This is a big win, because the previous solution required the client to explicitly call teardown() to close the file. Now that the logger is an object, the file can be closed when the object is destructed, which will happen when the program ends.

The implementation follows. Notice that the actual log() methods remain unchanged, except for the fact that they are no longer static. The constructor and destructor are called automatically because the class contains an instance of itself as a static member. Because they are private, no external code can create or delete a Logger.

/**

*Logger.cpp

*Implementation of a singleton logger class

*/

#include <string> #include “Logger.h”

using namespace std;

const string Logger::kLogLevelDebug = “DEBUG”; const string Logger::kLogLevelInfo = “INFO”; const string Logger::kLogLevelError = “ERROR”;

const char* const Logger::kLogFileName = “log.out”;

//The static instance will be constructed when the program starts and

//destructed when it ends.

Logger Logger::sInstance;

Logger& Logger::instance()

{

return sInstance;

}

Logger::~Logger()

{

mOutputStream.close();

}

Logger::Logger()

758