The Visitor Pattern – part 4: complete visitor

This article is one part of a series about the Visitor pattern. Please read the previous articles to get an overview about the pattern and to get the needed basics to understand this article.

The example implementation of this article is focused on the Visitor pattern. To keep the example as short as possible I intentional ignored some mandatory programming guidelines. Therefore, please keep in mind that the implementation should show the idea of the Visitor pattern but it ignores things like const correctness, private members or RAII (resource acquisition is initialization).

 

Complete visitor

Within the previous articles we have seen two use cases for the Visitor pattern: enumeration and dispatching. For each use case, we have implemented an according Visitor. In the context of these implementations we have seen that the Visitor pattern is over-engineering for the single use cases. There exist lightweight patterns exactly matching such single tasks. But if the two uses cases come together, the Visitor pattern may be appropriate. Therefore, only in this case we have a “complete” Visitor which unites enumeration and dispatching features.

Like in the previous examples we start with the interfaces.

class Book;
class Radio;
class Cheese;

class IVisitor
{
public:
  virtual void InstanceReceiver(Book* book) = 0;
  virtual void InstanceReceiver(Radio* radio) = 0;
  virtual void InstanceReceiver(Cheese* cheese) = 0;
};

class IVisitable
{
public:
  virtual void GetInstance(IVisitor* visitor) = 0;
};

 

Again, we want to use the three different article elements based on a common base class.

class Article : IVisitable
{
public:
  int mPrice;

  virtual void GetInstance(IVisitor* visitor) {};
};

class Book : public Article
{
public:
  Book(std::string title) : mTitle(title) {};

  std::string mTitle;

  void GetInstance(IVisitor* visitor)
  {
    visitor->InstanceReceiver(this);
  }
};

class Radio : public Article
{
public:
  Radio(std::string manufacturer, std::string model)
    : mManufacturer(manufacturer), mModel(model){};

  std::string mManufacturer;
  std::string mModel;

  void GetInstance(IVisitor* visitor)
  {
    visitor->InstanceReceiver(this);
  }
};

class Cheese : public Article
{
public:
  Cheese(std::string manufacturer, std::string name)
    : mManufacturer(manufacturer), mName(name){};

  std::string mManufacturer;
  std::string mName;

  void GetInstance(IVisitor* visitor)
  {
    visitor->InstanceReceiver(this);
  }
};

 

As in the enumeration example we will store the elements within a data container which contains the history of orders.

class Order
{
public:
  Order(Article* article, int count) : mArticle(article), mCount(count) {};

  Article* mArticle;
  int mCount;
};

class DailyOrders
{
public:
  std::vector mOrders;
  std::string mDate;
};

class OrdersHistory
{
public:
  std::vector mDailyOrders;
};

 

For the enumeration feature, we will use two generic enumerator Visitors. One will enumerate all elements and the other one will additionally filter distinct elements and therefore enumerate each object instance only once.

class ElementsEnumerator : IVisitor
{
public:
  void EnumerateAll(OrdersHistory& ordersHistory)
  {
    for (auto& dailyOrders : ordersHistory.mDailyOrders)
    {
      std::for_each(dailyOrders.mOrders.begin(), dailyOrders.mOrders.end(),
        [&](Order& order){order.mArticle->GetInstance(this); });
    }
  }
};

class DistinctElementsEnumerator : IVisitor
{
public:
  void EnumerateAll(OrdersHistory& ordersHistory)
  {
    mAlreadyEnumerated.clear();

    for (auto& dailyOrders : ordersHistory.mDailyOrders)
    {
      for (auto& order : dailyOrders.mOrders)
      {
        auto iterator = std::find(mAlreadyEnumerated.begin(), mAlreadyEnumerated.end(), reinterpret_cast(order.mArticle));
        if (iterator == mAlreadyEnumerated.end())
        {
          mAlreadyEnumerated.push_back(reinterpret_cast(order.mArticle));
          order.mArticle->GetInstance(this);
        }
      }
    }
  }

private:
  std::vector mAlreadyEnumerated;
};

 

Bases on the generic enumeration Visitors we can implement the query functions as dispatching Visitors. Here we have two queries. One will list the complete order history and the other one an overview with the ordered elements.

class OrderedArticlesHistoryQuery : public ElementsEnumerator
{
public:

  std::vector ExecuteQuery(OrdersHistory& ordersHistory)
  {
    EnumerateAll(ordersHistory);

    return mDescriptions;
  }

  void InstanceReceiver(Book* book)
  {
    mDescriptions.push_back("Book: " + book->mTitle);
  }

  void InstanceReceiver(Radio* radio)
  {
    mDescriptions.push_back("Radio: " + radio->mManufacturer + ", " + radio->mModel);
  }

  void InstanceReceiver(Cheese* cheese)
  {
    mDescriptions.push_back("Cheese: " + cheese->mManufacturer + ", " + cheese->mName);
  }

private:
  std::vector mDescriptions;
};


class OrderedArticlesQuery : public DistinctElementsEnumerator
{
public:

  std::vector ExecuteQuery(OrdersHistory& ordersHistory)
  {
    EnumerateAll(ordersHistory);

    return mDescriptions;
  }

  void InstanceReceiver(Book* book)
  {
    mDescriptions.push_back("Book: " + book->mTitle);
  }

  void InstanceReceiver(Radio* radio)
  {
    mDescriptions.push_back("Radio: " + radio->mManufacturer + ", " + radio->mModel);
  }

  void InstanceReceiver(Cheese* cheese)
  {
    mDescriptions.push_back("Cheese: " + cheese->mManufacturer + ", " + cheese->mName);
  }

private:
  std::vector mDescriptions;
};

 

Now we can use the Visitor within an example. At first, we create a data history element container and then we use the two query Visitors to get the according information about the ordered articles.

int _tmain(int argc, _TCHAR* argv[])
{
  // prepare data
  Book* book = new Book("My book");
  Radio* radio = new Radio("My manufacturer", "My model");
  Cheese* cheese = new Cheese("My manufacturer", "My name");

  DailyOrders dailyOrders1;
  DailyOrders dailyOrders2;

  dailyOrders1.mDate = "20180101";
  dailyOrders1.mOrders.push_back(Order(book, 3));
  dailyOrders1.mOrders.push_back(Order(radio, 4));
  dailyOrders1.mOrders.push_back(Order(cheese, 2));

  dailyOrders2.mDate = "20180102";
  dailyOrders2.mOrders.push_back(Order(book, 3));
  dailyOrders2.mOrders.push_back(Order(cheese, 2));  

  OrdersHistory ordersHistory;
  ordersHistory.mDailyOrders.push_back(dailyOrders1);
  ordersHistory.mDailyOrders.push_back(dailyOrders2);

  // execute queries
  std::vector titles;

  titles = OrderedArticlesHistoryQuery().ExecuteQuery(ordersHistory);

  std::cout << "---Complete Articles History---" << std::endl;

  std::for_each(titles.begin(), titles.end(),
    [](std::string title){std::cout << title << std::endl;; });

  titles = OrderedArticlesQuery().ExecuteQuery(ordersHistory);

  std::cout << std::endl;
  std::cout << "---Ordered Articles---" << std::endl;

  std::for_each(titles.begin(), titles.end(),
    [](std::string title){std::cout << title << std::endl;; });

  // delete data
  delete book;
  delete radio;
  delete cheese;

  return 0;
}

 

Assessment

The implemented Visitor contains enumeration and dispatching of the elements. The implementation has the same complexity as the ones seen in the previous examples which solved one use case only. But this time the disadvantage of the complex pattern is exceeded by the advantage that we can solve both use cases at once. The Visitor pattern will match perfectly with this kind of implementation task and therefore it can show its strength.

But of course, there are still some open questions. For example: Will it be easy to add new elements? How can we write queries which have to access some other properties of the data container, like the count of ordered elements? Can we reuse enumerator Visitors?

We will look at these questions soon and see whether the Visitor pattern is suitable to solve these use cases or whether we reach the borders of the pattern.

 

Outlook

Within the next articles we want to analyze some advanced topics, like reusable enumerator Visitors or Visitors which can access not visitable elements of the data container.

Werbung
Dieser Beitrag wurde unter C++ veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s