Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
B.Eckel - Thinking in C++, Vol.2, 2nd edition.pdf
Скачиваний:
50
Добавлен:
08.05.2013
Размер:
2.09 Mб
Скачать

sumValue(bin); purge(bin); // Cleanup

} ///:~

In the factory method Trash::factory( ), the determination of the exact type of object is simple, but you can imagine a more complicated system in which factory( ) uses an elaborate algorithm. The point is that it’s now hidden away in one place, and you know to come to this place to make changes when you add new types.

The creation of new objects is now more general in main( ), and depends on “real” data (albeit created by another generator, driven by random numbers). The generator object is created, telling it the maximum type number and the largest “data” value to produce. Each call to the generator creates an Info object which is passed into Trash::factory( ), which in turn produces some kind of Trash object and returns the pointer that’s added to the vector<Trash*> bin.

The constructor for the Info object is very specific and restrictive in this example. However, you could also imagine a vector of arguments into the Info constructor (or directly into a factory( ) call, for that matter). This requires that the arguments be parsed and checked at runtime, but it does provide the greatest flexibility.

You can see from this code what “vector of change” problem the factory is responsible for solving: if you add new types to the system (the change), the only code that must be modified is within the factory, so the factory isolates the effect of that change.

A pattern for prototyping creation

A problem with the above design is that it still requires a central location where all the types of the objects must be known: inside the factory( ) method. If new types are regularly being added to the system, the factory( ) method must be changed for each new type. When you discover something like this, it is useful to try to go one step further and move all of the activities involving that specific type – including its creation – into the class representing that type. This way, the only thing you need to do to add a new type to the system is to inherit a single class.

To move the information concerning type creation into each specific type of Trash, the “prototype” pattern will be used. The general idea is that you have a master container of objects, one of each type you’re interested in making. The “prototype objects” in this container are used only for making new objects. In this case, we’ll name the object-creation member function clone( ). When you’re ready to make a new object, presumably you have some sort of information that establishes the type of object you want to create. The factory( ) method (it’s not required that you use factory with prototype, but they commingle nicely) moves through the master container comparing your information with whatever appropriate information is in the prototype objects in the master container. When a match is found, factory( ) returns a clone of that object.

In this scheme there is no hard-coded information for creation. Each object knows how to expose appropriate information to allow matching, and how to clone itself. Thus, the factory( ) method doesn’t need to be changed when a new type is added to the system.

Chapter 16: Design Patterns

476

The prototypes will be contained in a static vector<Trash*> called prototypes. This is a private member of the base class Trash. The friend class TrashPrototypeInit is responsible for putting the Trash* prototypes into the prototype list.

You’ll also note that the Info class has changed. It now uses a string to act as type identification information. As you shall see, this will allow us to read object information from a file when creating Trash objects.

//: C09:Trash.h

// Base class for Trash recycling examples #ifndef TRASH_H

#define TRASH_H #include <iostream> #include <exception> #include <vector> #include <string>

class TypedBin; // For a later example class Visitor; // For a later example

class Trash { double _weight;

void operator=(const Trash&); Trash(const Trash&);

public:

Trash(double wt) : _weight(wt) {} virtual double value() const = 0;

double weight() const { return _weight; } virtual ~Trash() {}

class Info { std::string _id; double _data;

public:

Info(std::string ident, double dat) : _id(ident), _data(dat) {}

double data() const { return _data; } std::string id() const { return _id; } friend std::ostream& operator<<(

std::ostream& os, const Info& info) { return os << info._id << ':' << info._data;

}

};

protected:

//Remainder of class provides support for

//prototyping:

static std::vector<Trash*> prototypes;

Chapter 16: Design Patterns

477

friend class TrashPrototypeInit; Trash() : _weight(0) {}

public:

static Trash* factory(const Info& info); virtual std::string id() = 0; // type ident virtual Trash* clone(const Info&) = 0;

// Stubs, inserted for later use: virtual bool addToBin(std::vector<TypedBin*>&) {

return false;

}

virtual void accept(Visitor&) {};

};

#endif // TRASH_H ///:~

The basic part of the Trash class remains as before. The rest of the class supports the prototyping pattern. The id( ) member function returns a string that can be compared with the id( ) of an Info object to determine whether this is the prototype that should be cloned (of course, the evaluation can be much more sophisticated than that if you need it). Both id( ) and clone( ) are pure virtual functions so they must be overridden in derived classes.

The last two member functions, addToBin( ) and accept( ), are “stubs” which will be used in later versions of the trash sorting problem. It’s necessary to have these virtual functions in the base class, but in the early examples there’s no need for them, so they are not pure virtuals so as not to intrude.

The factory( ) member function has the same declaration, but the definition is what handles the prototyping. Here is the implementation file:

//: C09:Trash.cpp {O} #include "Trash.h" using namespace std;

Trash* Trash::factory(const Info& info) { vector<Trash*>::iterator it;

for(it = prototypes.begin();

it != prototypes.end(); it++) {

//Somehow determine the new type

//to create, and clone one:

if (info.id() == (*it)->id()) return (*it)->clone(info);

}

cerr << "Prototype not found for "

<<info << endl;

//"Default" to first one in the vector: return (*prototypes.begin())->clone(info);

}///:~

Chapter 16: Design Patterns

478

The string inside the Info object contains the type name of the Trash to be created; this string is compared to the id( ) values of the objects in prototypes. If there’s a match, then that’s the object to create.

Of course, the appropriate prototype object might not be in the prototypes list. In this case, the return in the inner loop is never executed and you’ll drop out at the end, where a default value is created. It might be more appropriate to throw an exception here.

As you can see from the code, there’s nothing that knows about specific types of Trash. The beauty of this design is that this code doesn’t need to be changed, regardless of the different situations it will be used in.

Trash subclasses

To fit into the prototyping scheme, each new subclass of Trash must follow some rules. First, it must create a protected default constructor, so that no one but TrashPrototypeInit may use it. TrashPrototypeInit is a singleton, creating one and only one prototype object for each subtype. This guarantees that the Trash subtype will be properly represented in the prototypes container.

After defining the “ordinary” member functions and data that the Trash object will actually use, the class must also override the id( ) member (which in this case returns a string for comparison) and the clone( ) function, which must know how to pull the appropriate information out of the Info object in order to create the object correctly.

Here are the different types of Trash, each in their own file.

//: C09:Aluminum.h

// The Aluminum class with prototyping #ifndef ALUMINUM_H

#define ALUMINUM_H #include "Trash.h"

class Aluminum : public Trash { static double val;

protected: Aluminum() {}

friend class TrashPrototypeInit; public:

Aluminum(double wt) : Trash(wt) {} double value() const { return val; } static void value(double newVal) {

val = newVal;

}

std::string id() { return "Aluminum"; } Trash* clone(const Info& info) {

return new Aluminum(info.data());

}

Chapter 16: Design Patterns

479

};

#endif // ALUMINUM_H ///:~

//: C09:Paper.h

// The Paper class with prototyping #ifndef PAPER_H

#define PAPER_H #include "Trash.h"

class Paper : public Trash { static double val;

protected: Paper() {}

friend class TrashPrototypeInit; public:

Paper(double wt) : Trash(wt) {} double value() const { return val; } static void value(double newVal) {

val = newVal;

}

std::string id() { return "Paper"; } Trash* clone(const Info& info) {

return new Paper(info.data());

}

};

#endif // PAPER_H ///:~

//: C09:Glass.h

// The Glass class with prototyping #ifndef GLASS_H

#define GLASS_H #include "Trash.h"

class Glass : public Trash { static double val;

protected: Glass() {}

friend class TrashPrototypeInit; public:

Glass(double wt) : Trash(wt) {} double value() const { return val; } static void value(double newVal) {

val = newVal;

}

std::string id() { return "Glass"; }

Chapter 16: Design Patterns

480

Trash* clone(const Info& info) { return new Glass(info.data());

}

};

#endif // GLASS_H ///:~

And here’s a new type of Trash:

//: C09:Cardboard.h

// The Cardboard class with prototyping #ifndef CARDBOARD_H

#define CARDBOARD_H #include "Trash.h"

class Cardboard : public Trash { static double val;

protected: Cardboard() {}

friend class TrashPrototypeInit; public:

Cardboard(double wt) : Trash(wt) {} double value() const { return val; } static void value(double newVal) {

val = newVal;

}

std::string id() { return "Cardboard"; } Trash* clone(const Info& info) {

return new Cardboard(info.data());

}

};

#endif // CARDBOARD_H ///:~

The static val data members must be defined and initialized in a separate code file:

//: C09:TrashStatics.cpp {O}

//Contains the static definitions for

//the Trash type's "val" data members #include "Trash.h"

#include "Aluminum.h" #include "Paper.h" #include "Glass.h" #include "Cardboard.h"

double Aluminum::val = 1.67; double Paper::val = 0.10; double Glass::val = 0.23;

Chapter 16: Design Patterns

481

double Cardboard::val = 0.14;

///:~

There’s one other issue: initialization of the static data members. TrashPrototypeInit must create the prototype objects and add them to the static Trash::prototypes vector. So it’s very important that you control the order of initialization of the static objects, so the prototypes vector is created before any of the prototype objects, which depend on the prior existence of prototypes. The most straightforward way to do this is to put all the definitions in a single file, in the order in which you want them initialized.

TrashPrototypeInit must be defined separately because it inserts the actual prototypes into the vector, and throughout the chapter we’ll be inheriting new types of Trash from the existing types. By making this one class in a separate file, a different version can be created and linked in for the new situations, leaving the rest of the code in the system alone.

//: C09:TrashPrototypeInit.cpp {O}

//Performs initialization of all the prototypes.

//Create a different version of this file to

//make different kinds of Trash.

#include "Trash.h" #include "Aluminum.h" #include "Paper.h" #include "Glass.h" #include "Cardboard.h"

// Allocate the static member object: std::vector<Trash*> Trash::prototypes;

class TrashPrototypeInit { Aluminum a;

Paper p; Glass g; Cardboard c;

TrashPrototypeInit() { Trash::prototypes.push_back(&a); Trash::prototypes.push_back(&p); Trash::prototypes.push_back(&g); Trash::prototypes.push_back(&c);

}

static TrashPrototypeInit singleton;

};

TrashPrototypeInit

TrashPrototypeInit::singleton; ///:~

Chapter 16: Design Patterns

482

Соседние файлы в предмете Численные методы
  • #
    08.05.20133.99 Mб22A.Menezes, P.van Oorschot,S.Vanstone - HANDBOOK OF APPLIED CRYPTOGRAPHY.djvu
  • #
  • #
    08.05.20135.91 Mб24B.Eckel - Thinking in Java, 3rd edition (beta).pdf
  • #
  • #
    08.05.20136.09 Mб17D.MacKay - Information Theory, Inference, and Learning Algorithms.djvu
  • #
    08.05.20133.85 Mб15DIGITAL Visual Fortran ver.5.0 - Programmers Guide to Fortran.djvu
  • #
    08.05.20131.84 Mб12E.A.Lee, P.Varaiya - Structure and Interpretation of Signals and Systems.djvu