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

A basic OOP design principle is “Use data members for variation in state, use polymorphism for variation in behavior.” Your first thought might be that the grab( ) member function certainly behaves differently for a vector that holds Paper than for one that holds Glass. But what it does is strictly dependent on the type, and nothing else.

1.TbinList holds a set of Tbin pointers, so that sort( ) can iterate through the Tbins when it’s looking for a match for the Trash object you’ve handed it.

2.sortBin( ) allows you to pass an entire Tbin in, and it moves through the Tbin, picks out each piece of Trash, and sorts it into the appropriate specific Tbin. Notice the genericity of this code: it doesn’t change at all if new types are added. If the bulk of your code doesn’t need changing when a new type is added (or some other change occurs) then you have an easily-extensible system.

3.Now you can see how easy it is to add a new type. Few lines must be changed to support the addition. If it’s really important, you can squeeze out even more by further manipulating the design.

4.One member function call causes the contents of bin to be sorted into the respective specifically-typed bins.

Applying double dispatching

The above design is certainly satisfactory. Adding new types to the system consists of adding or modifying distinct classes without causing code changes to be propagated throughout the system. In addition, RTTI is not as “misused” as it was in Recycle1.cpp. However, it’s possible to go one step further and eliminate RTTI altogether from the operation of sorting the trash into bins.

To accomplish this, you must first take the perspective that all type-dependent activities – such as detecting the type of a piece of trash and putting it into the appropriate bin – should be controlled through polymorphism and dynamic binding.

The previous examples first sorted by type, then acted on sequences of elements that were all of a particular type. But whenever you find yourself picking out particular types, stop and think. The whole idea of polymorphism (dynamically-bound member function calls) is to handle type-specific information for you. So why are you hunting for types?

The multiple-dispatch pattern demonstrated at the beginning of this chapter uses virtual functions to determine all type information, thus eliminating RTTI.

Implementing the double dispatch

In the Trash hierarchy we will now make use of the “stub” virtual function addToBin( ) that was added to the base class Trash but unused up until now. This takes an argument of a

TypedBin

add(Aluminum)

add(Paper)

add(Glass)

Chapter 16: Design Patterns 492 add(Cardboard)

AluminumBin

 

PaperBin

 

GlassBin

 

CardboardBin

 

 

 

 

 

 

 

add(Aluminum)

 

add(Paper)

 

add(Glass)

 

add(Cardboard)

 

 

 

 

 

 

 

container of TypedBin. A Trash object uses addToBin( ) with this container to step through and try to add itself to the appropriate bin, and this is where you’ll see the double dispatch.

Trash

addToBin(TypedBin[])

Aluminum

Paper

Glass

 

Cardboard

addToBin()

addToBin()

addToBin()

 

addToBin()

 

 

 

 

 

The new hierarchy is TypedBin, and it contains its own member function called add( ) that is also used polymorphically. But here’s an additional twist: add( ) is overloaded to take arguments of the different types of Trash. So an essential part of the double dispatching scheme also involves overloading (or at least having a group of virtual functions to call; overloading happens to be particularly convenient here).

//: C09:TypedBin.h #ifndef TYPEDBIN_H #define TYPEDBIN_H #include "Trash.h"

#include "Aluminum.h" #include "Paper.h" #include "Glass.h" #include "Cardboard.h" #include <vector>

//Template to generate double-dispatching

//trash types by inheriting from originals: template<class TrashType>

class DD : public TrashType { protected:

DD() : TrashType(0) {}

friend class TrashPrototypeInit; public:

DD(double wt) : TrashType(wt) {}

bool addToBin(std::vector<TypedBin*>& tb) { for(int i = 0; i < tb.size(); i++)

if(tb[i]->add(this)) return true;

return false;

}

//Override clone() to create this new type: Trash* clone(const Trash::Info& info) {

Chapter 16: Design Patterns

493

return new DD(info.data());

}

};

//vector<Trash*> that knows how to

//grab the right type

class TypedBin : public std::vector<Trash*> { protected:

bool addIt(Trash* t) { push_back(t);

return true;

}

public:

virtual bool add(DD<Aluminum>*) { return false;

}

virtual bool add(DD<Paper>*) { return false;

}

virtual bool add(DD<Glass>*) { return false;

}

virtual bool add(DD<Cardboard>*) { return false;

}

};

// Template to generate specific TypedBins: template<class TrashType>

class BinOf : public TypedBin { public:

// Only overrides add() for this specific type: bool add(TrashType* t) { return addIt(t); }

};

#endif // TYPEDBIN_H ///:~

In each particular subtype of Aluminum, Paper, Glass, and Cardboard, the addToBin( ) member function is implemented, but it looks like the code is exactly the same in each case. The code in each addToBin( ) calls add( ) for each TypedBin object in the array. But notice the argument: this. The type of this is different for each subclass of Trash, so the code is different. So this is the first part of the double dispatch, because once you’re inside this member function you know you’re Aluminum, or Paper, etc. During the call to add( ), this information is passed via the type of this. The compiler resolves the call to the proper overloaded version of add( ). But since tb[i] produces a pointer to the base type TypedBin,

Chapter 16: Design Patterns

494

this call will end up calling a different member function depending on the type of TypedBin that’s currently selected. That is the second dispatch.

You can see that the overloaded add( ) methods all return false. If the member function is not overloaded in a derived class, it will continue to return false, and the caller (addToBin( ), in this case) will assume that the current Trash object has not been added successfully to a container, and continue searching for the right container.

In each of the subclasses of TypedBin, only one overloaded member function is overridden, according to the type of bin that’s being created. For example, CardboardBin overrides add(DD<Cardboard>). The overridden member function adds the Trash pointer to its container and returns true, while all the rest of the add( ) methods in CardboardBin continue to return false, since they haven’t been overridden. With C++ templates, you don’t have to explicitly write the subclasses or place the addToBin( ) member function in Trash.

To set up for prototyping the new types of trash, there must be a different initializer file:

//: C09:DDTrashPrototypeInit.cpp {O} #include "TypedBin.h"

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

std::vector<Trash*> Trash::prototypes;

class TrashPrototypeInit { DD<Aluminum> a; DD<Paper> p;

DD<Glass> g; DD<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; ///:~

Here’s the rest of the program:

//: C09:DoubleDispatch.cpp //{L} DDTrashPrototypeInit

//{L} fillBin Trash TrashStatics

Chapter 16: Design Patterns

495

//Using multiple dispatching to handle more than

//one unknown type during a member function call #include "TypedBin.h"

#include "fillBin.h" #include "sumValue.h" #include "../purge.h" #include <iostream> #include <fstream> using namespace std;

ofstream out("DoubleDispatch.out");

class TrashBinSet

: public vector<TypedBin*> {

public:

 

TrashBinSet() {

 

push_back(new

BinOf<DD<Aluminum> >);

push_back(new

BinOf<DD<Paper> >);

push_back(new

BinOf<DD<Glass> >);

push_back(new

BinOf<DD<Cardboard> >);

};

void sortIntoBins(vector<Trash*>& bin) { vector<Trash*>::iterator it;

for(it = bin.begin(); it != bin.end(); it++) // Perform the double dispatch: if(!(*it)->addToBin(*this))

cerr << "Couldn't add " << *it << endl;

}

~TrashBinSet() { purge(*this); }

};

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

//fillBin() still works, without changes, but

//different objects are cloned: fillBin("Trash.dat", bin);

//Sort from the master bin into the

//individually-typed bins: bins.sortIntoBins(bin); TrashBinSet::iterator it;

for(it = bins.begin(); it != bins.end(); it++) sumValue(**it);

//... and for the master bin

sumValue(bin);

purge(bin);

Chapter 16: Design Patterns

496

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