Difference between revisions of "Sheep and Herds in C++"

From Wiki**3

m (Class <tt>Sheep</tt>)
m (Class <tt>WhiteSheep</tt>)
Line 112: Line 112:
 
=== Class <tt>WhiteSheep</tt> ===
 
=== Class <tt>WhiteSheep</tt> ===
  
The following are the contents of file <tt>BlackSheep.h</tt>.
+
The following are the contents of file <tt>WhiteSheep.h</tt>.
 
<cpp>
 
<cpp>
 
#ifndef __WHITESHEEP_H__
 
#ifndef __WHITESHEEP_H__

Revision as of 22:36, 2 March 2008

The Problem (in Portuguese)

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 ele viu-se obrigado a dividir as ovelhas em ebanhos, 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.

Implementation of Part 1.

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 almanack). 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 lenght). Groups will be aggregations of the base concept: in this way, we have great flexbility 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.

Class Shearable

The following are the contents of file Shearable.h (since it is a small class, it is completely defined in the header file). <cpp>

  1. ifndef __SHEARABLE_H__
  2. define __SHEARABLE_H__

class Shearable { public:

 virtual ~Shearable() {}
 /**
  * Shearing operation.
  */
 virtual void shear() = 0;
 /**
  * Hair growth: represents the passing of time.
  */
 virtual void growWool() = 0;
 /**
  *
  */
 virtual void add(Shearable *s) = 0;

};

  1. endif

</cpp>

Class Sheep

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). The following are the contents of file Sheep.h. 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). <cpp>

  1. ifndef __SHEEP_H__
  2. define __SHEEP_H__
  1. include "Shearable.h"

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"; }

};

  1. endif

</cpp>

Class BlackSheep

The following are the contents of file BlackSheep.h. Since the wool is long, shearing keeps going until it is short. <cpp> ifndef __BLACKSHEEP_H__

  1. define __BLACKSHEEP_H__
  1. include <iostream>
  2. include "Sheep.h"

class BlackSheep : public Sheep { public:

 /**
  * Initialization of black sheep with long wool.
  */
 BlackSheep() : Sheep(100) {}
 /**
  * Shear sheep while wool is long.
  */
 void shear() {
   std::cerr << "BlackSheep::shear" << std::endl;
   while (_woolLength > 10) _woolLength -= 10;
 }
 /**
  * Grow wool.
  */
 void growWool() {
   std::cerr << "BlackSheep::growWool" << std::endl;
   _woolLength += 100;
 }

};

  1. endif

</cpp>

Class WhiteSheep

The following are the contents of file WhiteSheep.h. <cpp>

  1. ifndef __WHITESHEEP_H__
  2. define __WHITESHEEP_H__
  1. include <iostream>
  2. include "Sheep.h"

class WhiteSheep : public Sheep { public:

 WhiteSheep() : Sheep(10) {}
 void shear() {
   std::cerr << "WhiteSheep::shear" << std::endl;
   if (_woolLength > 5) _woolLength -= 5;
 }
 void growWool() {
   std::cerr << "WhiteSheep::growWool" << std::endl;
   _woolLength += 1;
 }

};

  1. endif

</cpp>

The main function

The following is an example for testing the model: we create two sheep and three groups. <cpp>

  1. include "WhiteSheep.h"
  2. include "BlackSheep.h"
  3. include "Herd.h"

int main() {

 Shearable *s1 = new WhiteSheep();
 Shearable *s2 = new BlackSheep();
 Shearable *g1 = new Herd();
 Shearable *g2 = new Herd();
 Shearable *g3 = new Herd();
 g1->add(s1);  g2->add(s2);  g3->add(g1);  g3->add(g2);
 s1->shear();  s1->growWool();
 s2->shear();  s2->growWool();
 g1->shear();  g2->shear();  g3->growWool();
 g3->shear();
 delete s1; delete s2;
 delete g1; delete g2; delete g3;

} </cpp>

Testing Part 1.

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.

Implementation of Part 2.

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...).

Class Animal

The following code corresponds to file Animal.h. Note the general accept function (it accepts any visitor). <cpp>

  1. ifndef __ANIMAL_H__
  2. define __ANIMAL_H__
  1. include "Visitor.h"

class Animal { public:

 virtual ~Animal() {}
 /**
  * Accept operation.
  * @param visitor a visitor.
  */
 virtual void accept(Visitor *v) {
   v->processAnimal(this);
 }
 /**
  * Add a member to a group.
  * @param a a member of the group.
  */
 virtual void add(Animal *s) = 0;

};

  1. endif

</cpp>

Class Sheep

The following code corresponds to file Sheep.h. Note the general accept function (it accepts any visitor). A few extra functions were defined (for use by the visitors). <cpp>

  1. ifndef __SHEEP_H__
  2. define __SHEEP_H__
  1. include "Animal.h"

class Sheep : public Animal { protected:

 int _woolLength; // should be private...

public:

 /**
  * Initialize wool length.
  * @param woolLength the initial wool length.
  */
 Sheep(int woolLength) : _woolLength(woolLength) {}
 /**
  * @return wool length.
  */
 int woolLength() const { return _woolLength; }
 /**
  * Increment/decrement wool length.
  * @param delta the amount to increment/decrement.
  */
 void incrementWoolLength(int delta) { _woolLength += delta; }
 /**
  * Accept operation.
  * @param visitor a visitor.
  */
 virtual void accept(Visitor *v) {
   v->processSheep(this);
 }
 /**
  * Cannot add, but must do something...
  * @param a member to be added to a group (ignored)
  */
 void add(Animal *a) { throw "unsupported operation"; }

};

  1. endif

</cpp>

Class BlackSheep

The following code corresponds to file BlackSheep.h. Note the general accept function (it accepts any visitor). <cpp>

  1. ifndef __BLACKSHEEP_H__
  2. define __BLACKSHEEP_H__
  1. include <iostream>
  2. include "Sheep.h"

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

};

  1. endif

</cpp>

Class WhiteSheep

The following code corresponds to file WhiteSheep.h. Note the general accept function (it accepts any visitor). <cpp>

  1. ifndef __WHITESHEEP_H__
  2. define __WHITESHEEP_H__
  1. include <iostream>
  2. include "Sheep.h"

class WhiteSheep : public Sheep { public:

 WhiteSheep() : Sheep(10) {}
 /**
  * Accept operation.
  * @param visitor a visitor.
  */
 virtual void accept(Visitor *v) {
   v->processWhiteSheep(this);
 }

};

  1. endif

</cpp>

Class Goat

The following code corresponds to file Goat.h. Note the general accept function (it accepts any visitor). <cpp>

  1. ifndef __GOAT_H__
  2. define __GOAT_H__
  1. include "Animal.h"

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"; }

};

  1. endif

</cpp>

Class Herd

The following code corresponds to file Herd.h. 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. <cpp>

  1. ifndef __HERD_H__
  2. define __HERD_H__
  1. include <vector>
  2. include "Animal.h"

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

};

  1. endif

</cpp>

Class Visitor

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. <cpp>

  1. ifndef __VISITOR_H__
  2. define __VISITOR_H__

// do not include (just declare classes) class Animal; class Sheep; class BlackSheep; class WhiteSheep; class Goat; class Herd;

/**

* Definition of a visitor: it knows how to visit all
* members of the Animals hierarchy.
*/

class Visitor { public:

 virtual void processAnimal(Animal *) = 0;
 virtual void processSheep(Sheep *) = 0;
 virtual void processBlackSheep(BlackSheep *) = 0;
 virtual void processWhiteSheep(WhiteSheep *) = 0;
 virtual void processGoat(Goat *) = 0;
 virtual void processHerd(Herd *) = 0;

};

  1. endif

</cpp>

Class ShearingVisitor

The following code corresponds to file ShearingVisitor.h, for defining how to shear instances of each concept (note how goats are unaffected). <cpp>

  1. ifndef __SHEARINGVISITOR_H__
  2. define __SHEARINGVISITOR_H__
  1. include "Visitor.h"
  2. include "BlackSheep.h"
  3. include "WhiteSheep.h"
  4. include "Goat.h"
  5. include "Herd.h"

/**

* Definition of a visitor: it knows how to visit all
* members of the Animals hierarchy.
*/

class ShearingVisitor : public Visitor { public:

 void processAnimal(Animal*) { /* do nothing */ }
 void processSheep(Sheep*) { /* do nothing */ }
 void processBlackSheep(BlackSheep *b) {
   std::cerr << "BlackSheep::shear" << std::endl;
   while (b->woolLength() > 10) b->incrementWoolLength(-10);
 }
 void processWhiteSheep(WhiteSheep *w) {
   std::cerr << "WhiteSheep::shear" << std::endl;
   if (w->woolLength() > 5) w->incrementWoolLength(-5);
 }
 void processGoat(Goat*) { /* do nothing */ }
 void processHerd(Herd *h) {
   for (size_t hx = 0; hx < h->size(); hx++)
     h->at(hx)->accept(this);
 }

};

  1. endif

</cpp>

Class GrowWoolVisitor

The following code corresponds to file GrowWoolVisitor.h, for defining how to grow wool on sheep (note how goats are unaffected). <cpp>

  1. ifndef __GROWWOOLVISITOR_H__
  2. define __GROWWOOLVISITOR_H__
  1. include "Visitor.h"
  2. include "BlackSheep.h"
  3. include "WhiteSheep.h"
  4. include "Goat.h"
  5. include "Herd.h"

/**

* 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);
 }

};

  1. endif

</cpp>

Class MilkingVisitor

The following code corresponds to file MilkingVisitor.h. Note how all animals are affected. <cpp>

  1. ifndef __MILKINGVISITOR_H__
  2. define __MILKINGVISITOR_H__
  1. include "Visitor.h"
  2. include "BlackSheep.h"
  3. include "WhiteSheep.h"
  4. include "Goat.h"
  5. include "Herd.h"

/**

* 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);
 }

};

  1. endif

</cpp>

The main function

The following code corresponds to the main function for testing animals and their visitors. <cpp>

  1. include "WhiteSheep.h"
  2. include "BlackSheep.h"
  3. include "Goat.h"
  4. include "Herd.h"
  1. include "ShearingVisitor.h"
  2. include "GrowWoolVisitor.h"
  3. include "MilkingVisitor.h"

int main() {

 Animal *a1 = new WhiteSheep();
 Animal *a2 = new BlackSheep();
 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>

Compiling and running

As before, since all the classes are small, we used only header files and need only to compile the main2.cpp file.

g++ -o main2 main2.cpp

The output of the main function is as shown (note that we always apply the visitors to the larger group).

WhiteSheep::shear
BlackSheep::shear
WhiteSheep::growWool
BlackSheep::growWool
WhiteSheep::milking
BlackSheep::milking
Goat::milking