Sheep and Herds in C++

From Wiki**3

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

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

Class "Shearable"

Note that, since it is a small class, it is completely defined in the header file.

File Shearable.h
#ifndef __SHEARABLE_H__
#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;
};

#endif

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

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
#ifndef __SHEEP_H__
#define __SHEEP_H__

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

#endif

Class "BlackSheep"

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

File BlackSheep.h
#ifndef __BLACKSHEEP_H__
#define __BLACKSHEEP_H__

#include <iostream>
#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;
  }
};

#endif

Class "WhiteSheep"

File WhiteSheep.h
#ifndef __WHITESHEEP_H__
#define __WHITESHEEP_H__

#include <iostream>
#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;
  }
};

#endif

Class "Herd"

File Herd.h
#ifndef __HERD_H__
#define __HERD_H__

#include <vector>
#include <iostream>
#include "Shearable.h"

class Herd : public Shearable {
  std::vector<Shearable*> _shearable;

public:
  /**
   * Shear all members of the herd.
   */
  void shear() {
    std::cerr << "Herd::shear: starting..." << std::endl;
    for (size_t sx = 0; sx < _shearable.size(); sx++)
      _shearable[sx]->shear();
    std::cerr << "Herd::shear: done." << std::endl;
  }
  /**
   * Grow wool on all members (i.e., time passes).
   */
  void growWool() {
    std::cerr << "Herd::growWool: starting..." << std::endl;
    for (size_t sx = 0; sx < _shearable.size(); sx++)
      _shearable[sx]->growWool();
    std::cerr << "Herd::growWool: done." << std::endl;
  }
  /**
   * Add a member to the herd.
   * @param s the new member of the herd.
   */
  void add(Shearable *s) { _shearable.push_back(s); }
};

#endif

The main function

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

File main.cpp
#include "WhiteSheep.h"
#include "BlackSheep.h"
#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;
}

Compiling and running

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 what type they are to be able to send them to 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"

Note the general accept function (it accepts any visitor).

File Animal.h
#ifndef __ANIMAL_H__
#define __ANIMAL_H__

#include "Visitor.h"

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

#endif

Class "Sheep"

Note the general accept function (it accepts any visitor). A few extra functions were defined (for use by the visitors).

File Sheep.h
#ifndef __SHEEP_H__
#define __SHEEP_H__

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

  /**
   * Cannot add, but must do something...
   * @param a member to be added to a group (ignored)
   */
  void add(Animal *a) { throw "unsupported operation"; }
};

#endif

Class "BlackSheep"

Note the general accept function (it accepts any visitor).

File BlackSheep.h
#ifndef __BLACKSHEEP_H__
#define __BLACKSHEEP_H__

#include <iostream>
#include "Sheep.h"

class BlackSheep : public Sheep {
public:
  /**
   * Initialization of black sheep with long wool.
   */
  BlackSheep() : Sheep(100) {}

  /**
   * Accept operation.
   * @param visitor a visitor.
   */
  void accept(Visitor *v) {
    v->processBlackSheep(this);
  }

};

#endif

Class "WhiteSheep"

Note the general accept function (it accepts any visitor).

File WhiteSheep.h
#ifndef __WHITESHEEP_H__
#define __WHITESHEEP_H__

#include <iostream>
#include "Sheep.h"

class WhiteSheep : public Sheep {
public:
  WhiteSheep() : Sheep(10) {}

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

};

#endif

Class "Goat"

Note the general accept function (it accepts any visitor).

File Goat.h
#ifndef __GOAT_H__
#define __GOAT_H__

#include "Animal.h"

class Goat : public Animal {
public:
  /**
   * Accept operation.
   * @param visitor a visitor.
   */
  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"; }
};

#endif

Class "Herd"

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
#ifndef __HERD_H__
#define __HERD_H__

#include <vector>
#include <iostream>
#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.
   */
  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); }
};

#endif

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.

File Visitor.h
#ifndef __VISITOR_H__
#define __VISITOR_H__

// do not include (just declare classes)
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 ~Visitor() {}
  virtual void processBlackSheep(BlackSheep *) = 0;
  virtual void processWhiteSheep(WhiteSheep *) = 0;
  virtual void processGoat(Goat *) = 0;
  virtual void processHerd(Herd *) = 0;
};

#endif

Class "ShearingVisitor"

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
#ifndef __SHEARINGVISITOR_H__
#define __SHEARINGVISITOR_H__

#include "Visitor.h"
#include "BlackSheep.h"
#include "WhiteSheep.h"
#include "Goat.h"
#include "Herd.h"

/**
 * Definition of a visitor: it knows how to visit all
 * members of the Animals hierarchy.
 */
class ShearingVisitor : public Visitor {
public:
  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);
  }
};

#endif

Class "GrowWoolVisitor"

The following code corresponds to class GrowWoolVisitor, for defining how to grow wool on sheep (note how goats are unaffected).

File GrowWoolVisitor.h
#ifndef __GROWWOOLVISITOR_H__
#define __GROWWOOLVISITOR_H__

#include "Visitor.h"
#include "BlackSheep.h"
#include "WhiteSheep.h"
#include "Goat.h"
#include "Herd.h"

/**
 * Definition of a visitor: it knows how to visit all
 * members of the Animals hierarchy.
 */
class GrowWoolVisitor : public Visitor {
public:
  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);
  }
};

#endif

Class "MilkingVisitor"

The following code corresponds to class MilkingVisitor. Note how all animals are affected.

File MilkingVisitor.h
#ifndef __MILKINGVISITOR_H__
#define __MILKINGVISITOR_H__

#include "Visitor.h"
#include "BlackSheep.h"
#include "WhiteSheep.h"
#include "Goat.h"
#include "Herd.h"

/**
 * Definition of a visitor: it knows how to visit all
 * members of the Animals hierarchy.
 */
class MilkingVisitor : public Visitor {
public:
  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);
  }
};

#endif

The main function

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

File main.cpp
#include "BlackSheep.h"
#include "WhiteSheep.h"
#include "Goat.h"
#include "Herd.h"

#include "ShearingVisitor.h"
#include "GrowWoolVisitor.h"
#include "MilkingVisitor.h"

int main() {
  Animal *a1 = new BlackSheep();
  Animal *a2 = new WhiteSheep();
  Animal *a3 = new Goat();

  Animal *g1 = new Herd();
  g1->add(a1);

  Animal *g2 = new Herd();
  g2->add(a2); 
  g2->add(a3);

  Animal *g3 = new Herd();
  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;
}

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