(→Class Visitor) |
(→Class ShearingVisitor) |
||
Line 512: | Line 512: | ||
}} | }} | ||
− | === Class | + | === Class "ShearingVisitor" === |
− | The following code corresponds to file | + | The following code corresponds to file '''ShearingVisitor.h''', for defining how to shear instances of each concept (note how goats are unaffected). |
+ | {{CollapsedCode|File ShearingVisitor.h| | ||
<cpp> | <cpp> | ||
#ifndef __SHEARINGVISITOR_H__ | #ifndef __SHEARINGVISITOR_H__ | ||
Line 550: | Line 551: | ||
#endif | #endif | ||
</cpp> | </cpp> | ||
+ | }} | ||
=== Class <tt>GrowWoolVisitor</tt> === | === Class <tt>GrowWoolVisitor</tt> === |
Um pastor tem muitas ovelhas de várias espécies (brancas de lã curta, pretas de lã comprida), das quais vende lã para para a indústria de tecelagem. O pastor vive numa região muito acidentada, onde o espaço para pastagens não abunda e viu-se obrigado a dividir as ovelhas em rebanhos, cada um dos quais ele leva a pastar para uma dada região (quase todos plataformas empoleiradas em penhascos).
Quando lhe perguntam como se consegue organizar, quando quer tosquiar todas as ovelhas, o pastor explica que tosquia as ovelhas recursivamente, de acordo com um método que viu num almanaque: considera o grupo de rebanhos que possui e assegura que tosquiar um grupo de rebanhos é como tosquiar cada rebanho. Se alguém faz a pergunta "como se tosquia um rebanho?", ele responde com um ar de quem tem mais que fazer que é como quem tosquia cada ovelha. O método funciona tão bem que ele está a pensar alugar terrenos nas montanhas vizinhas e levar para lá novos grupos de rebanhos. Quando quiser tosquiar tudo, apenas tem de tosquiar o grupo formado pelos grupos de rebanhos em cada zona.
1. Represente a organização de ovelhas, modelando cada espécie, assim como os grupos, considerando a abordagem do pastor à operação de tosquiar.
Passado algum tempo, o pastor comprou algumas cabras, por forma a aproveitar melhor os penhascos, já que as cabras podiam alcançar zonas inacessíveis às ovelhas. Para tornar o negócio rentável, o pastor passou a ordenhar as ovelhas e as cabras para obtenção de leite e posterior produção de queijo. O negócio parecia ideal, não fosse a complicação introduzida: os rebanhos tinham animais misturados e não se podiam aplicar as operações indiscriminadamente a todos os animais (não se podiam tosquiar as cabras, por exemplo).
Felizmente, o pastor conseguiu encontrar a solução no almanaque: só precisava de comprar algumas máquinas que fossem capazes de desempenhar cada tarefa: cada máquina visitaria cada grupo e cada rebanho e, dependendo da respectiva natureza, aplicaria a operação apropriada (ou ignoraria a entidade).
2. Represente a organização das máquinas de processamento pecuário e as alterações a fazer na modelação anterior.
When implementing 1. we need to take into account the operation that is common to all entities: shearing (tosquiar). A simple implementation is obtained through inheritance as described by the Composite design pattern (as indicated by the shepherd's almanac). The top concept will represent something that can be sheared. We will call this concept "Shearable". Each sheep species will be a subclass of this concept, implementing it in some way (e.g. by modifying hair length). Groups will be aggregations of the base concept: in this way, we have great flexibility in distributing sheep among the herds, or herds among the various herd groups. The shearing operation will be carried out simply by iterating through each group's members and shearing it: if it is a sheep, then it will be sheared; if not, the iterative process will be repeated.
Note that, since it is a small class, it is completely defined in the header file.
File Shearable.h |
---|
{{{2}}} |
Class Sheep exists only to abstract features common to individual sheep, but not to herds. Wool length should be private and accessible only through a dedicated interface (we did not concern ourselves with this aspect, since we wanted to keep the example simple).
Note that, since sheep are shearable and might, in some contexts, be confused with herds, we need to provide some safety net for the add operation (in case it is wrongly called).
File Sheep.h |
---|
<cpp>
class Sheep : public Shearable { protected: int _woolLength; // should be private... public: /** * Initialize wool length. * @param woolLength the initial wool length. */ Sheep(int woolLength) : _woolLength(woolLength) {} /** * Cannot add, but must do something... * @param s member to be added to a group (ignored) */ void add(Shearable *s) { throw "unsupported operation"; } };
</cpp> |
The following are the contents of file BlackSheep.h. Since the wool is long, shearing keeps going until it is short.
File BlackSheep.h |
---|
{{{2}}} |
File WhiteSheep.h |
---|
{{{2}}} |
File Herd.h |
---|
{{{2}}} |
The following is an example for testing the model: we create two sheep and three groups.
File main.cpp |
---|
{{{2}}} |
We need just to compile the main function file (since all the others are just header files).
g++ -o main main.cpp
The output of running the program is the following:
WhiteSheep::shear WhiteSheep::growWool BlackSheep::shear BlackSheep::growWhool Herd::shear: starting... WhiteSheep::shear Herd::shear: done. Herd::shear: starting... BlackSheep::shear Herd::shear: done. Herd::growWool: starting... Herd::growWool: starting... WhiteSheep::growWool Herd::growWool: done. Herd::growWool: starting... BlackSheep::growWhool Herd::growWool: done. Herd::growWool: done. Herd::shear: starting... Herd::shear: starting... WhiteSheep::shear Herd::shear: done. Herd::shear: starting... BlackSheep::shear Herd::shear: done. Herd::shear: done.
Part 2 cannot be implemented using the method in part 1: we need to distinguish between the animals to know what to do in each case. The problem is that we still want to benefit from not caring exactly wha type they are to be able to send them ot their pastures (as before). We need to resort to the other pattern in the almanack: the Visitor pattern. Instead of each class supporting a specific operation like shearing or growing hair, they will leave those concerns to third parties. In a sense, they "outsource" those functions to visitor objects. If some operation is not to be supported, then the visitor object does nothing in the corresponding case (e.g. shearing a goat).
The changes to the model are few: we define a new class, called Animal, which will abstract both Shearable and Goat. Herds will group animals and other herds (in this sense, a herd is like an animal...).
Note the general accept function (it accepts any visitor).
File Animal.h |
---|
{{{2}}} |
Note the general accept function (it accepts any visitor). A few extra functions were defined (for use by the visitors).
File Sheep.h |
---|
{{{2}}} |
Note the general accept function (it accepts any visitor).
File BlackSheep.h |
---|
<cpp>
class BlackSheep : public Sheep { public: /** * Initialization of black sheep with long wool. */ BlackSheep() : Sheep(100) {} /** * Accept operation. * @param visitor a visitor. */ virtual void accept(Visitor *v) { v->processBlackSheep(this); } };
</cpp> |
Note the general accept function (it accepts any visitor).
File WhiteSheep.h |
---|
<cpp>
class WhiteSheep : public Sheep { public: WhiteSheep() : Sheep(10) {} /** * Accept operation. * @param visitor a visitor. */ virtual void accept(Visitor *v) { v->processWhiteSheep(this); } };
</cpp> |
Note the general accept function (it accepts any visitor).
File Goat.h |
---|
<cpp>
class Goat : public Animal { public: /** * Accept operation. * @param visitor a visitor. */ virtual void accept(Visitor *v) { v->processGoat(this); } /** * Cannot add, but must do something... * @param a member to be added to a group (ignored) */ void add(Animal *a) { throw "unsupported operation"; } };
</cpp> |
Note the general accept function (it accepts any visitor). Since the visitors know when they are processing this class, we can add special methods for them to use.
File Herd.h |
---|
<cpp>
class Herd : public Animal { std::vector<Animal*> _animals; public: /** * @return herd size. */ size_t size() const { return _animals.size(); } /** * @param index the element's position. * @return element at given index. */ Animal *at(size_t index) { return _animals.at(index); } /** * Accept operation. * @param visitor a visitor. */ virtual void accept(Visitor *v) { v->processHerd(this); } /** * Add a member to the herd. * @param a the new member of the herd. */ void add(Animal *a) { _animals.push_back(a); } };
</cpp> |
The following code corresponds to file Visitor.h, the superclass of all animal visitors. Note that it declares an interface for processing each and everyone of the previous classes. If each specific visitor does not want to process some case, then it may define the corresponding method to be empty or to report an error. We declared the classes (rather than including them) to avoid dependency-related complications.
File Visitor.h |
---|
{{{2}}} |
The following code corresponds to file ShearingVisitor.h, for defining how to shear instances of each concept (note how goats are unaffected).
File ShearingVisitor.h |
---|
{{{2}}} |
The following code corresponds to file GrowWoolVisitor.h, for defining how to grow wool on sheep (note how goats are unaffected). <cpp>
/**
* Definition of a visitor: it knows how to visit all * members of the Animals hierarchy. */
class GrowWoolVisitor : public Visitor { public:
void processAnimal(Animal*) { /* do nothing */ } void processSheep(Sheep*) { /* do nothing */ } void processBlackSheep(BlackSheep *b) { std::cerr << "BlackSheep::growWool" << std::endl; b->incrementWoolLength(100); } void processWhiteSheep(WhiteSheep *w) { std::cerr << "WhiteSheep::growWool" << std::endl; w->incrementWoolLength(1); } void processGoat(Goat*) { /* do nothing */ } void processHerd(Herd *h) { for (size_t hx = 0; hx < h->size(); hx++) h->at(hx)->accept(this); }
};
</cpp>
The following code corresponds to file MilkingVisitor.h. Note how all animals are affected. <cpp>
/**
* Definition of a visitor: it knows how to visit all * members of the Animals hierarchy. */
class MilkingVisitor : public Visitor { public:
void processAnimal(Animal*) { /* do nothing */ } void processSheep(Sheep*) { /* do nothing */ } void processBlackSheep(BlackSheep *b) { std::cerr << "BlackSheep::milking" << std::endl; } void processWhiteSheep(WhiteSheep *w) { std::cerr << "WhiteSheep::milking" << std::endl; } void processGoat(Goat*) { std::cerr << "Goat::milking" << std::endl; } void processHerd(Herd *h) { for (size_t hx = 0; hx < h->size(); hx++) h->at(hx)->accept(this); }
};
</cpp>
The following code corresponds to the main function for testing animals and their visitors. <cpp>
int main() {
Animal *a1 = new BlackSheep(); Animal *a2 = new WhiteSheep(); Animal *a3 = new Goat(); Animal *g1 = new Herd(); Animal *g2 = new Herd(); Animal *g3 = new Herd();
g1->add(a1); g2->add(a2); g2->add(a3); g3->add(g1); g3->add(g2);
Visitor *v1 = new ShearingVisitor(); Visitor *v2 = new GrowWoolVisitor(); Visitor *v3 = new MilkingVisitor();
g3->accept(v1); g3->accept(v2); g3->accept(v3);
delete a1; delete a2; delete a3; delete g1; delete g2; delete g3; delete v1; delete v2; delete v3;
} </cpp>
As before, since all the classes are small, we used only header files and need only to compile the main2.cpp file.
g++ -o main main.cpp
The output of the main function is as shown (note that we always apply the visitors to the larger group).
BlackSheep::shear WhiteSheep::shear BlackSheep::growWool WhiteSheep::growWool BlackSheep::milking WhiteSheep::milking Goat::milking