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

Applying Design Patterns

{

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

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

}

}

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

{

mOutputStream << 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);

}

}

Using a Singleton

The two programs below display the usage of the two different versions of the Logger class.

// TestStaticLogger.cpp

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

int main(int argc, char** argv)

{

Logger::log(“test message”, Logger::kLogLevelDebug);

vector<string> items; items.push_back(“item1”); items.push_back(“item2”);

Logger::log(items, Logger::kLogLevelError);

Logger::teardown();

}

// TestTrueSingletonLogger.cpp

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

int main(int argc, char** argv)

{

Logger::instance().log(“test message”, Logger::kLogLevelDebug);

vector<string> items;

759

Chapter 26

items.push_back(“item1”); items.push_back(“item2”);

Logger::instance().log(items, Logger::kLogLevelError);

}

Both programs have the same functionality. After executing, the file log.out should contain the following lines:

DEBUG: test message

ERROR: item1

ERROR: item2

The Factor y Pattern

A factory in real life constructs tangible objects, such as tables or cars. Similarly, a factory in object-oriented programming constructs objects. When you use factories in your program, portions of code that want to create a particular object ask the factory for an instance of the object instead of calling the object constructor themselves. For example, an interior decorating program might have a FurnitureFactory object. When part of the code needs a piece of furniture such as a table, it would call the createTable() method of the FurnitureFactory object, which would return a new table.

At first glance, factories seem to lead to complicated designs without clear benefits. It appears that you’re only adding another layer of complexity to the program. Instead of calling createTable() on a FurnitureFactory, you could simply create a new Table object directly. However, factories can actually be quite useful. Instead of creating various objects all over the program, you centralize the object creation for a particular domain. This localization is often a better model of real-world creation of objects.

Another benefit of factories is that you can use them alongside class hierarchies to construct objects without knowing their exact class. As you’ll see in the following example, factories can run parallel to class hierarchies.

Example: A Car Factory Simulation

In the real world, when you talk about driving a car, you can do so without referring to the specific type of car. You could be discussing a Toyota or a Ford. It doesn’t matter, because both Toyotas and Fords are drivable. Now, suppose that you want a new car. You would then need to specify whether you wanted a Toyota or a Ford, right? Not always. You could just say “I want a car,” and depending on where you were, you would get a specific car. If you said, “I want a car” in a Toyota factory, chances are you’d get a Toyota. (Or you’d get arrested, depending on how you asked). If you said, “I want a car” in a Ford factory, you’d get a Ford.

The same concepts apply to C++ programming. The first concept, of a generic car that’s drivable, is nothing new: it’s standard polymorphism, which you learned about in Chapter 3. You could write an abstract Car class that defines a drive() method. Both Toyota and Ford could be subclasses of the Car class, as shown in Figure 26-1.

760

Applying Design Patterns

Car

Toyota Ford

Figure 26-1

Your program could drive Cars without knowing whether they were really Toyotas or Fords. However, with standard object-oriented programming, the one place that you’d need to specify Toyota or Ford

is when you create the car. Here, you would need to call the constructor for one or the other. You can’t just say, “I want a car.” However, suppose that you also had a parallel class hierarchy of car factories. The CarFactory superclass could define a virtual buildCar() method. The ToyotaFactory and FordFactory subclasses would override the buildCar() method to build a Toyota or a Ford. Figure 26-2 shows the CarFactory hierarchy.

CarFactory

ToyotaFactory FordFactory

Figure 26-2

Now, suppose that there is one CarFactory object in a program. When code in the program, such as a car dealer, wants a new car, it calls buildCar() on the CarFactory object. Depending on whether that car factory was really a ToyotaFactory or a FordFactory, the code would get either a Toyota or a Ford. Figure 26-3 shows the objects in a car dealer program using a ToyotaFactory:

 

Requests "Car"

ToyotaFactory

CarDealer

Returns "Car"

BuildsToyota

 

 

Figure 26-3

Figure 26-4 shows the same program, but with a FordFactory instead of a ToyotaFactory. Note that the CarDealer object and its relationship with the factory stay the same:

 

Requests "Car"

FordFactory

CarDealer

Returns "Car"

BuildsFord

Figure 26-4

761

Chapter 26

The main benefit of this approach is that factories abstract the object creation process: you can easily substitute a different factory in your program. Just as you can use polymorphism with the created objects, you can use polymorphism with factories: when you ask the car factory for a car, you might not know whether it’s a Toyota factory or a Ford factory, but either way it will give you a Car that you can drive. This approach leads to easily extensible programs: simply changing the factory instance can allow the program to work on a completely different set of objects and classes.

Implementation of a Factory

One reason for using factories is if the type of the object you want to create depends on some condition. For example, if you are a dealer who needs a car right away, you might want to put your order into the factory that has the fewest requests, regardless of whether the car you eventually get is a Toyota or a Ford. The following implementation will show how to write such factories in C++.

The first thing you’ll need is the hierarchy of cars. To keep this example simple, the Car class simply has an abstract method that returns a description of the car. Both Car subclasses are also defined in the following example, using inline methods to return their descriptions.

/**

* Car.h

*

*/

#include <iostream>

class Car

{

public:

virtual void info() = 0;

};

class Ford : public Car

{

public:

virtual void info() { std::cout << “Ford” << std::endl; }

};

class Toyota : public Car

{

public:

virtual void info() { std::cout << “Toyota” << std::endl; }

};

The CarFactory base class is a bit more interesting. Each factory keeps track of the number of cars in production. When the public requestCar() method is called, the number of cars in production at the factory is increased by one, and calling the pure virtual createCar() method returns a new car. The idea is that individual factories will override createCar() to return the appropriate type of car. The factory itself implements requestCar(), which takes care of updating the number of cars in production. CarFactory also provides a public method to query the number of cars being produced at each factory.

762

Applying Design Patterns

The class definitions for the CarFactory subclass are shown here:

/**

* CarFactory.h */

// For this example, the Car class is assumed to already exist. #include “Car.h”

class CarFactory

{

public:

CarFactory();

Car* requestCar();

int getNumCarsInProduction() const;

protected:

virtual Car* createCar() = 0;

private:

int mNumCarsInProduction;

};

class FordFactory : public CarFactory

{

protected:

virtual Car* createCar();

};

class ToyotaFactory : public CarFactory

{

protected:

virtual Car* createCar();

};

As you can see, the subclasses simply override createCar() to return the specific type of car that they produce. The implementation of the CarFactory hierarchy is shown here:

/**

* CarFactory.cpp */

#include “CarFactory.h”

//Initialize the count to zero when the factory is created. CarFactory::CarFactory() : mNumCarsInProduction(0) {}

//Increment the number of cars in production and return the

//new car.

Car* CarFactory::requestCar()

{

763