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

receptacle in the first place? (indeed, this is the whole enigma of recycling). In this program it might be easy to repair, but sometimes a system’s structure and flexibility can benefit greatly from downcasting.

The program satisfies the design requirements: it works. This may be fine as long as it’s a one-shot solution. However, a good program will evolve over time, so you must ask: what if the situation changes? For example, cardboard is now a valuable recyclable commodity, so how will that be integrated into the system (especially if the program is large and complicated). Since the above type-check coding in the switch statement and in the RTTI statements could be scattered throughout the program, you’d have to go find all that code every time a new type was added, and if you miss one the compiler won’t help you.

The key to the misuse of RTTI here is that every type is tested. If you’re only looking for a subset of types because that subset needs special treatment, that’s probably fine. But if you’re hunting for every type inside a switch statement, then you’re probably missing an important point, and definitely making your code less maintainable. In the next section we’ll look at how this program evolved over several stages to become much more flexible. This should prove a valuable example in program design.

Improving the design

The solutions in Design Patterns are organized around the question “What will change as this program evolves?” This is usually the most important question that you can ask about any design. If you can build your system around the answer, the results will be two-pronged: not only will your system allow easy (and inexpensive) maintenance, but you might also produce components that are reusable, so that other systems can be built more cheaply. This is the promise of object-oriented programming, but it doesn’t happen automatically; it requires thought and insight on your part. In this section we’ll see how this process can happen during the refinement of a system.

The answer to the question “What will change?” for the recycling system is a common one: more types will be added to the system. The goal of the design, then, is to make this addition of types as painless as possible. In the recycling program, we’d like to encapsulate all places where specific type information is mentioned, so (if for no other reason) any changes can be localized inside those encapsulations. It turns out that this process also cleans up the rest of the code considerably.

“Make more objects”

This brings up a general object-oriented design principle that I first heard spoken by Grady Booch: “If the design is too complicated, make more objects.” This is simultaneously counterintuitive and ludicrously simple, and yet it’s the most useful guideline I’ve found. (You might observe that “make more objects” is often equivalent to “add another level of indirection.”) In general, if you find a place with messy code, consider what sort of class would clean things up. Often the side effect of cleaning up the code will be a system that has better structure and is more flexible.

Chapter 16: Design Patterns

471

Consider first the place where Trash objects are created. In the above example, we’re conveniently using a generator to create the objects. The generator nicely encapsulates the creation of the objects, but the neatness is an illusion because in general we’ll want to create the objects based on something more than a random number generator. Some information will be available which will determine what kind of Trash object this should be. Because you generally need to make your objects by examining some kind of information, if you’re not paying close attention you may end up with switch statements (as in TrashGen) or cascaded if statements scattered throughout your code. This is definitely messy, and also a place where you must change code whenever a new type is added. If new types are commonly added, a better solution is a single member function that takes all of the necessary information and produces an object of the correct type, already upcast to a Trash pointer. In Design Patterns this is broadly referred to as a creational pattern (of which there are several). The specific pattern that will be applied here is a variant of the Factory Method (“method” being a more OOPish way to refer to a member function). Here, the factory method will be a static member of Trash, but more commonly it is a member function that is overridden in the derived class.

The idea of the factory method is that you pass it the essential information it needs to know to create your object, then stand back and wait for the pointer (already upcast to the base type) to pop out as the return value. From then on, you treat the object polymorphically. Thus, you never even need to know the exact type of object that’s created. In fact, the factory method hides it from you to prevent accidental misuse. If you want to use the object without polymorphism, you must explicitly use RTTI and casting.

But there’s a little problem, especially when you use the more complicated approach (not shown here) of making the factory method in the base class and overriding it in the derived classes. What if the information required in the derived class requires more or different arguments? “Creating more objects” solves this problem. To implement the factory method, the Trash class gets a new member function called factory( ). To hide the creational data, there’s a new class called Info that contains all of the necessary information for the factory( ) method to create the appropriate Trash object. Here’s a simple implementation of Info:

class Info { int type;

// Must change this to add another type: static const int maxnum = 3;

double data; public:

Info(int typeNum, double dat)

: type(typeNum % maxnum), data(dat) {}

};

An Info object’s only job is to hold information for the factory( ) method. Now, if there’s a situation in which factory( ) needs more or different information to create a new type of Trash object, the factory( ) interface doesn’t need to be changed. The Info class can be changed by adding new data and new constructors, or in the more typical object-oriented fashion of subclassing.

Chapter 16: Design Patterns

472

Here’s the second version of the program with the factory method added. The object-counting code has been removed; we’ll assume proper cleanup will take place in all the rest of the examples.

//: C09:Recycle2.cpp

// Adding a factory method #include "sumValue.h" #include "../purge.h" #include <fstream> #include <vector>

#include <typeinfo> #include <cstdlib> #include <ctime> using namespace std;

ofstream out("Recycle2.out");

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() {}

//Nested class because it's tightly coupled

//to Trash:

class Info { int type;

// Must change this to add another type: static const int maxnum = 3;

double data;

friend class Trash; public:

Info(int typeNum, double dat)

: type(typeNum % maxnum), data(dat) {}

};

static Trash* factory(const Info& info);

};

class Aluminum : public Trash { static double val;

public:

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

Chapter 16: Design Patterns

473

static void value(double newval) { val = newval;

}

~Aluminum() { out << "~Aluminum\n"; }

};

double Aluminum::val = 1.67F;

class Paper : public Trash { static double val;

public:

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

val = newval;

}

~Paper() { out << "~Paper\n"; }

};

double Paper::val = 0.10F;

class Glass : public Trash { static double val;

public:

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

val = newval;

}

~Glass() { out << "~Glass\n"; }

};

double Glass::val = 0.23F;

//Definition of the factory method. It must know

//all the types, so is defined after all the

//subtypes are defined:

Trash* Trash::factory(const Info& info) { switch(info.type) {

default: // In case of overrun case 0:

return new Aluminum(info.data); case 1:

return new Paper(info.data);

Chapter 16: Design Patterns

474

case 2:

return new Glass(info.data);

}

}

// Generator for Info objects: class InfoGen {

int typeQuantity; int maxWeight;

public:

InfoGen(int typeQuant, int maxWt)

: typeQuantity(typeQuant), maxWeight(maxWt) { srand(time(0));

}

Trash::Info operator()() {

return Trash::Info(rand() % typeQuantity, static_cast<double>(rand() % maxWeight));

}

};

int main() { vector<Trash*> bin;

//Fill up the Trash bin: InfoGen infoGen(3, 100); for(int i = 0; i < 30; i++)

bin.push_back(Trash::factory(infoGen())); vector<Aluminum*> alBin;

vector<Paper*> paperBin; vector<Glass*> glassBin;

vector<Trash*>::iterator sorter = bin.begin();

//Sort the Trash:

while(sorter != bin.end()) { Aluminum* ap =

dynamic_cast<Aluminum*>(*sorter);

Paper* pp = dynamic_cast<Paper*>(*sorter); Glass* gp = dynamic_cast<Glass*>(*sorter); if(ap) alBin.push_back(ap);

if(pp) paperBin.push_back(pp); if(gp) glassBin.push_back(gp); sorter++;

}

sumValue(alBin);

sumValue(paperBin);

sumValue(glassBin);

Chapter 16: Design Patterns

475

Соседние файлы в предмете Численные методы
  • #
    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