C# 7: binary literals, digit separators, out variables

Within this article I want to introduce some of the minor but helpful new features. These are binary literals, digit separators and out variables.

Binary Literal

So far, we could use decimal and hexadecimal literals in C#. With C# 7.0 a third type is supported, the binary literal.

static void Main(string[] args)
{
  int x = 42;       // decimal literal
  int y = 0x2A;     // hexadecimal literal
  int z = 0b101010; // binary literal
}

Digit Separators

Decimal, hexadecimal and binary literals may be difficult to read if they are long. With C# 7.0 we could use the underscore character within such literals as digit separators. The use of digit separators will not change the meaning of the literal. But you can increase the readability of your literals if you use them wisely.

static void Main(string[] args)
{
  int x = 1_542;          // decimal literal
  int y = 0x2A_BF_71_4D;  // hexadecimal literal
  int z = 0b1011_1010;    // binary literal

  int max = Math.Max(1_513, 2_712);
}

Out Variables inline declaration

With C#7 output variables can be declared inline directly when passing to the method.

static void Main(string[] args)
{
  // C# 6 style
  int x;
  DoSomething(out x);
  Console.WriteLine(x);

  // C# 7 style
  DoSomething(out int y);
  Console.WriteLine(y);
}

private static void DoSomething(out int value)
{
  value = 5;
}

In my opinion this feature is a matter of taste. On the one hand you become the possibility to slim down the source code but on the other hand the variable declaration will be hidden inside the method call. In some situations, the source code readability may be increased by using the inline declaration but in other cases the explicit declaration outside of the method will increase the readability. For example, in cases were the variable is used in several places within a complex function it may be better to declare it explicitly at the beginning of the function instead of using the hidden inline declaration.

Advertisements
Veröffentlicht unter C# | Kommentar hinterlassen

The Visitor Pattern – part 6: specialized visitors

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

Specialized visitors

Implementation of the Visitor pattern often tends to be create wide interfaces with a huge number of methods. This happens because normally there is a big number of visitable elements already on start of the implementation and this number will get increased during the project lifetime. Generally, you should avoid wide interfaces as they are often complex and difficult to use. As the Visitor pattern creates one kind of methods only, the complexity of the interface stays constant no matter whether you have ten or hundred interface methods. But on implementation of the Visitor you will feel the pain if you have to implement such a huge number of methods. For example, let’s say you want to implement a couple of queries which should work with a group of elements only. Even if you are interested in this element group only, you have to implement all other interface methods too. This may result in a huge number of empty method implementations.

But no fear, there is an easy way out of this situation. Instead to follow a “one fits it all” approach, you may implement specialized visitors. But before we think about this kind of implementation I want to mention another solution which is often mentioned in this context but which comes with a big disadvantage. To avoid the need to implement empty methods you could define empty visitor interfaces. Instead of this explicit interface methods, you will create an implicit method definition e.g. within a design document. Within your elements you will now be able to implement this implicit method or not. If you implement it you can use type cast to check whether the visitor supports this method and is able to receive the element instance. This kind of implementation removes the disadvantage of the empty methods but comes with the bid disadvantage of some kind of implicit interface definition somewhere in your project documentation. And of course, this is a good source for implementation errors. Therefore, I don’t recommend this kind of implementation.

Instead I would prefer specialized visitors. That means I want to create several visitors which operate on a group of elements only. Let’s think about the following element structure: you have a shop with several articles and within these articles you have some soup groups like food.

  • Article
    • Book
    • Food
      • Cheese
      • Sausage

Within this little tree structure, we can find three leaves: Book, Cheese and Sausage. These elements are good candidates for the visitable interface. Furthermore, we have two nodes –  Article and Food – which are good candidates for the visitor interface.

The following source code shows a possible implementation of the two different Visitors. At first we define the interfaces for the Article Visitor and the Food Visitor and the according visitable elements.

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

class IFoodVisitor
{
public:
  virtual void InstanceReceiver(Cheese* book) = 0;
  virtual void InstanceReceiver(Sausage* radio) = 0;  
};

class IVisitableArticle
{
public:
  virtual void GetInstance(IArticleVisitor* visitor) = 0;
};

class IVisitableFood
{
public:
  virtual void GetInstance(IFoodVisitor* visitor) = 0;
};

Based on these interfaces we can implement the date elements.

class Article : IVisitableArticle
{
public:
  int mPrice;

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

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

  std::string mTitle;

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

class Food : public Article, IVisitableFood
{
public:
  virtual void GetInstance(IFoodVisitor* visitor) {};
};

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

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

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

class Sausage : public Food
{
public:
  Sausage(std::string brandName)
    : mBrandName(brandName){};

  std::string mBrandName;

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

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

The implementation of the Visitors is done according to the examples we have seen in the previous articles. Therefore, the following code shows a template only and you can have a look into the previous articles for more information about implementation of the data containers, the Enumerator Visitors and the Query Visitors.

class MyFoodQuery : public IFoodVisitor
{
public:

  void ExecuteQuery(std::vector& foods)
  {    
  }

  void InstanceReceiver(Cheese* cheese)
  {
    // ...   
  }

  void InstanceReceiver(Sausage* sausage)
  {
    // ...
  }
  
private:
  std::vector mDescriptions;
};

Inherit Interfaces

Within the above example implementation of the interfaces you may see a possibility for a code optimization. The underlying elements which are covered by the Visitors exist in a base-derived relationship. If we have this kind of element hierarchy, the Visitor interfaces can also inherit from each other. So, we can use the “BaseVisitor” as base class of the “ArticleVisitor” to create a visitor interface hierarchy.

Interface Concept

During software design phase, you may want to decide about the structure of your Visitors. Should you create one Visitor for each group of elements or should you combine some of them or is it better to create one Visitor with all elements? Of course, this question is use case specific and depends on your application. Following I want to give you some common guidelines which may help you during software design.

Beside the “empty methods” the Visitor pattern comes with the downside of a bad maintainability in case the data elements change. If you must introduce new data elements, you must change all existing Visitors too. You can weaken both disadvantages if you create specialized interfaces because the data structure changes will affect a part of you Visitors only. As conclusion: if you have fix data elements and data structures you could use a small number of wide and general Visitor interfaces and if you have variable structures with a high possibility of changes, you should use specialized Visitor interfaces.

During software design phase, you can define the Visitor interfaces by using a top-down or bottom-up approach. The top-down approach starts at use case level. You should analyze your use cases and think about the concrete queries needed in these cases. At next you can think about the data elements needed in these queries. Based on the knowledge about the queries and their data elements, you can look for similarities and differences and create according use case specific Visitor interfaces.

If you don’t know the use cases and queries yet, or if you already know that there will be a lot more use cases in future, you can use the bottom-up approach. This method is based on the data. You should analyze the data structure and create Visitor interfaces which are suitable for software extensions and maintenances. As we already know, this can be solved with specialized Visitor interfaces. You should analyze the structure of your data. Like in the example above, your data normally has a tree-like inheritance architecture. Each tree node is a possible candidate for a Visitor interface. Of course, if you have a lot of nodes you may not create the same number of Visitors. Instead you should find the most important ones. Furthermore, you should create an architecture of inherited Visitors equal to the data structure. This will allow an easy integration of additional Visitors for nodes without Visitor in future.

Assessment

Often, the “one fits it all” approach results in a rigid software design which isn’t suitable in case of extensions or code maintenance. Therefore, you should create specialized Visitor patterns. The challenge is to find the right balance between too less wide interfaces and too much small interfaces. The top-down or bottom-up approach may help you during software design phase to define the interfaces according to the use cases and/or the data structure.

Outlook

Within the next articles we will think about the reusability of the different Visitors and remove the currently existing strict relationship between the enumerator and query Visitors. Furthermore, we will think about use cases which are often used as examples for the Visitor pattern but which could be implemented easier by using other patterns. This should help to avoid a misuse of the pattern, resulting in over-engineering. And finally, we will finish the article series with a summary of the whole topic.

Veröffentlicht unter C++ | Kommentar hinterlassen

The Visitor Pattern – part 5: extended enumerator 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).

 

Extended enumerator visitor

Within the previous articles we have seen the enumerator Visitor. This kind of Visitor is responsible to traverse a data structure and enumerate all elements. It is used by the query Visitors or algorithm Visitors.

As we have seen, this separation of concerns results in a clean code with reusable Visitors. But there may be one issue we didn’t consider so far. Data structures may not contain the elements only. The may contain additional information too. This additional information could be needed by the query Visitors but at the moment our implementation will enumerator the elements only and does not provide the additional information.

Let’s have a look at the example we used so far. We have defined the Visitor interface and created some element objects.

//-------------------------------------
// forward declaration
//-------------------------------------

class Book;
class Radio;
class Cheese;

//-------------------------------------
// interfaces
//-------------------------------------

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

//-------------------------------------
// Elements
//-------------------------------------

class Article : IVisitable
{
public:
  int mPrice;

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

class Book : public Article
{
public:
  Book(std::string title, int price) : mTitle(title)
  {
    mPrice = price;
  };

  std::string mTitle;

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

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

  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, int price, int importDuty)
    : mManufacturer(manufacturer), mName(name), mImportDuty(importDuty)
  {
    mPrice = price;
  };

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

  int mImportDuty;

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

 

As data container, we have a tree like structure which contains the history of ordered articles.

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

 

As you can see, this data container stores the elements and it contains additional information like the count and data of orders. A standard use case based on this data may be to calculate the total turnover. In this case we need the count-information stored within the order element. An easy solution is to create an extended enumerator Visitor. I call it “extended” because the enumerator will traverse over the elements and additional it will provide extended information about the data structure which stores the elements.

class ElementsEnumerator : IVisitor
{
public:
  void EnumerateAll(OrdersHistory& ordersHistory)
  {
    for (auto& dailyOrders : ordersHistory.mDailyOrders)
    {
      for (auto& order : dailyOrders.mOrders)
      {
        mActualOrder = ℴ
        order.mArticle->GetInstance(this);
      }
    }
  }

  int ActualOrderCount() { return mActualOrder->mCount; };

private:
  Order* mActualOrder;
};

 

This extended enumerator Visitor is quite simple. It is based on the standard enumerator Visitor we used so far and adds a property only. So, we can reuse or extend existing code. The extended enumerator Visitor can be used within the query to calculate the total turnover.

class TurnoverQuery : public ElementsEnumerator
{
public:

  int ExecuteQuery(OrdersHistory& ordersHistory)
  {
    mTurnover = 0;

    EnumerateAll(ordersHistory);

    return mTurnover;
  }

  void InstanceReceiver(Book* book)
  {
    mTurnover += book->mPrice * ActualOrderCount();
  }

  void InstanceReceiver(Radio* radio)
  {
    mTurnover += radio->mPrice * ActualOrderCount();
  }

  void InstanceReceiver(Cheese* cheese)
  {
    mTurnover += (cheese->mPrice + cheese->mImportDuty) * ActualOrderCount();
  }

private:
  int mTurnover;
};

And again, we have the same advantage. The query Visitors can be implemented in the same way as we already seen in the other examples. We do not reinvent the wheel or create a new concept. We use the existing concept and extend it with the new features.

The following code shows the example application which creates some data elements and executes the query.

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

  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
  int turnover = TurnoverQuery().ExecuteQuery(ordersHistory);

  std::cout << "turnover: " << turnover << std::endl;

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

  return 0;
}

 

Assessment

As mentioned before, the implementation has the big advantage of matching with the base implementation of the pattern. These kinds of extended enumerators are equal to the simple enumerators. This increases the readability of the source code and does not add new complexity. Furthermore, it allows to extend existing source code without impact to the queries or algorithms which use the existing enumerator.

The downside of the concept is the separation of the data accesses. The query must get the needed data over two different interfaces and principles. The main data, stored within the data element, will be forwarded to the query by the double dispatch mechanism. The additional data will be determined by accessing the according properties via single dispatch. This will result in a more complex data query mechanism.

An alternative would be to make the needed structure elements visitable too. In this case the standard dispatching could be used. But this will increase the complexity of the whole data access mechanism because new we have a system of dependent elements. So far, the elements are independent of each other. If we make a structure element visitable which contains visitable elements, we have a hierarchical structure of inner and outer visitable objects. Writing enumerator Visitors and query Visitors will now become more difficult as we always have to think about whether we want to visit the inner element, the outer element or both. This will become very complex in systems with a deep element hierarchy. Beside the complexity of the system, such a kind of implementation will inevitable result in specialized enumerators and queries.

 

Outlook

Within the next articles we will think about the reusability of the different Visitors and remove the currently existing strict relationship between the enumerator and query Visitors. Furthermore, we will think about use cases which are often used as examples for the Visitor pattern but which could be implemented easier by using other patterns. This should help to avoid a misuse of the pattern, resulting in over-engineering. And finally, we will finish the article series with a summary of the whole topic.

Veröffentlicht unter C++ | Kommentar hinterlassen

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.

Veröffentlicht unter C++ | Kommentar hinterlassen

The Visitor Pattern – part 3: dispatcher 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).

 

Dispatcher visitor

As mentioned within the first article of this series, one task of the Visitor pattern is to access the specific element type instances and provides these instance to a receiver. This dispatching is done by using double dispatch.

Within this article we want to look at a possible implementation of this dispatching task. Within the example, the data container stores the elements without to know their concrete types. We have three different element types, all based on a common base class. Like in the previous example we start by defining the Visitor 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;
};

 

At next we implement the element classes.

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

 

This time we will not implement a data container class. We use a standard vector instead. The abstract base Visitor to enumerate all elements is accordingly simple.

class ElementsEnumerator : IVisitor
{
public:
  void EnumerateAll(std::vector<Article>&amp; articles)
  {
    std::for_each(articles.begin(), articles.end(), [&amp;](Article* article){article-&gt;GetInstance(this); });
  }
};

 

Based on the enumerator we would implement a query Visitor. This query should get all article descriptions and therefore it needs to analyze the type specific properties. This will be done in the dispatcher functions of the Visitor.

class ArticleDescriptionsQuery : public ElementsEnumerator
{
public:

  std::vector ExecuteQuery(std::vector<Article>&amp; articles)
  {
    EnumerateAll(articles);

    return mDescriptions;
  }

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

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

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

private:
  std::vector mDescriptions;
};

 

We could now use this query within a little test application.

int _tmain(int argc, _TCHAR* argv[])
{
  // prepare data
  std::vector<Article> articles;
  articles.push_back(new Book("My book"));
  articles.push_back(new Radio("My manufacturer", "My model"));
  articles.push_back(new Cheese("My manufacturer", "My name"));

  // execute query
  std::vector descriptions;

  descriptions = ArticleDescriptionsQuery().ExecuteQuery(articles);

  std::for_each(descriptions.begin(), descriptions.end(), 
    [](std::string description){std::cout &lt;&lt; description &lt;&lt; std::endl;; });

  // delete data
  std::for_each(articles.begin(), articles.end(), [](Article* article){delete article; });

  return 0;
}

Solution without Visitor pattern

The implemented example shows a solution for a standard programming use case: we want to enumerate over elements which are stored as base class pointers and have to access their concrete type. Of course, this can be implemented by using some other techniques too. A popular solution is casting. Therefore, I want to spend some time and compare the Visitor pattern with common casting.

At first, we want to think about the advantages of casting. These advantages result in the fact that we can omit the complex Visitor pattern. So, we eliminate the issues which came with this complex pattern.

But on the other hand, the casting solution – as simple as it is – will add some issues too. At first, there are some technical disadvantages. A dynamic cast costs performance. And it gets around the strict type checking done by the compiler and therefore the chance for errors gets increased. At second there is a software design issue which may result in misunderstanding and errors. I want to call this design issue a “lying interface”. The component offers an interface which is used by a client. This explicit contract is expanded by some implicit agreements which are not part of the contract. So, hopefully both partners keep these agreements, otherwise we could result in undefined behavior of the application. So, what do I mean with “lying interfaces”? The component promises to deliver elements of base type and the client will use these elements. But within a hidden agreement the component promises to deliver derived type object instances. And the client does expect these derived types. So, the interface does not reflect the real contract between the component and the client. If it is an interface between two strong coupled objects within a component this is a minor issue because in this case the technical point of view matters. But if it is an interface which is used by another component this may result in big issues and errors because in this case the logical point of view independent of technical details matters. In such external interfaces, you may offer some type specific query methods instead of one method returning the whole element container with mixed types.

 

Assessment

The Visitor is used as dispatcher between the data container which manages elements as pointers of base class and the processing component which wants to execute functionality depending on the concrete types of the elements. The Visitor pattern allows an efficient implementation of this use case. But the pattern comes with the downside of the complexity and some other pattern specific disadvantages. Depending on the individual use case it may be over-engineering to use the Visitor pattern if the dispatching functionality is used only. In such cases you may use casting in component internal interfaces, or type specific query methods in external interfaces.

 

Outlook

Within the next article of this series I want to combine the two single use cases seen so far: the enumerator visitor and the dispatcher visitor.

Veröffentlicht unter C++ | Kommentar hinterlassen

The Visitor Pattern – part 2: enumerator 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).

 

Enumerator visitor

Within the previous article we learned that one purpose of the Visitor pattern is to enumerate over the elements of a complex data structure and to apply an algorithm an each of the elements. We also learned that these two concerns – enumeration and algorithm – should be separated into different classes.

Now we want to implement the example application, introduced within the previous article, by using the Visitor pattern. As we focus on the enumeration use case I called this article “enumerator visitor” but of course, the algorithm is implemented as visitor too.

Within the example we want to manage one article only: a book. But we will have a data container with a complex structure. This data container is used to store the order history for this book. Based on this order history we want to write two queries. On query should show all ordered books in chronological sequence. If a book was sold server times then it will be shown several times by the query too. As we also want to know which books of our collection were sold, we create a second query which should show them. In this case we need something like a distinct search within our order history.

Let’s start the implementation by defining the Visitor pattern interfaces.

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

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

 

At next we add the element and implement the “IVisitable” interface. Furthermore, we add the data container. The order history is a collection of daily orders. Each daily order contains the sold books of one day.

//-------------------------------------
// Element
//-------------------------------------

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

  std::string mTitle;
   
  void GetInstance(IVisitor* visitor)
  {
    visitor->InstanceReceiver(this);
  }
};

//-------------------------------------
// Container
//-------------------------------------

class Order
{
public:
  Order(Book book, int count) : mBook(book), mCount(count) {};

  Book mBook;
  int mCount;
};

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

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

 

Now it is time to implement the Visitors. As a base functionality, we need the enumeration of all elements. Thereafter the queries can be implemented based on these enumerations. So, let’s create an abstract base class for the enumerator does implement the traversing and is of type “IVisitor”. The visitor methods must be implemented by the queries. Therefore, the enumerator class is abstract and will not implement these methods. Instead the queries will derive from the enumerator. So, they inherit the traversing implementation and will add the visitor implementation.

//-------------------------------------
// Enumerator
//-------------------------------------

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.mBook.GetInstance(this); });      
    }
  }  
};


//-------------------------------------
// queries
//-------------------------------------

class OrderedBooksHistoryQuery : public ElementsEnumerator
{
public:
  
  std::vector ExecuteQuery(OrdersHistory& ordersHistory)
  {
    EnumerateAll(ordersHistory);

    return mTitles;
  }

  void InstanceReceiver(Book* book)
  {
    mTitles.push_back(book->mTitle);
  }

private:
  std::vector mTitles;
};

 

This will solve our first use case: show complete history. The second use case can be implemented in nearly the same way. We just have to add a check whether an element was already enumerated. To keep it simple we use the book title for this check.

//-------------------------------------
// Enumerator
//-------------------------------------

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(), order.mBook.mTitle);
        if (iterator == mAlreadyEnumerated.end())
        {
          mAlreadyEnumerated.push_back(order.mBook.mTitle);
          order.mBook.GetInstance(this);
        }        
      }
    }
  }

private:
  std::vector mAlreadyEnumerated;
};

//-------------------------------------
// queries
//-------------------------------------


class OrderedBooksQuery : public DistinctElementsEnumerator
{
public:

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

    return mTitles;
  }

  void InstanceReceiver(Book* book)
  {
    mTitles.push_back(book->mTitle);
  }

private:
  std::vector mTitles;
};

 

The following example application shows how to use the queries to analyze the data container.

int _tmain(int argc, _TCHAR* argv[])
{
  // prepare data
  Book book1("My book 1");
  Book book2("My book 2");
  Book book3("My book 3");
  Book book4("My book 4");
  Book book5("My book 5");

  DailyOrders dailyOrders1;
  DailyOrders dailyOrders2;

  dailyOrders1.mDate = "20180101";
  dailyOrders1.mOrders.push_back(Order(book1, 3));
  dailyOrders1.mOrders.push_back(Order(book2, 4));
  dailyOrders1.mOrders.push_back(Order(book4, 2));

  dailyOrders2.mDate = "20180102";
  dailyOrders2.mOrders.push_back(Order(book2, 3));
  dailyOrders2.mOrders.push_back(Order(book4, 2));
  dailyOrders2.mOrders.push_back(Order(book5, 2));

  OrdersHistory ordersHistory;
  ordersHistory.mDailyOrders.push_back(dailyOrders1);
  ordersHistory.mDailyOrders.push_back(dailyOrders2);
  
  // execute queries
  std::vector titles;

  titles = OrderedBooksHistoryQuery().ExecuteQuery(ordersHistory);

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

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

  titles = OrderedBooksQuery().ExecuteQuery(ordersHistory);

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

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

	return 0;
}

 

Assessment

The example shows some nice features of the Visitor pattern. We have a clear separation of the different concerns. New kinds of enumerations which may contain sorting or filter functionality can be added without changing the data container. Similarly, it is possible to add new algorithms.

But I think the implementation has two downsides too. We have implemented two algorithms but they are equal. They just inherit from a different enumerator. It would be nice if we have to implement the algorithm just once and can use it with any enumerator. Within a further article I want to show some other implementation techniques of the pattern which solve this issue.

The second issue of the implementation is not a technical one. It is a question of software design. Within this example the Visitor pattern is used to enumerator the elements. Beside this enumeration aspect it adds no further benefit. If we just want to enumerate items, we could use the Iterator pattern. Of course, the focus of this article was to show one aspect of the Visitor pattern only. Therefore, with respect to this aim the implementation is fine. I just mentioned this objection to keep in mind that the use of the Visitor pattern may be over-engineering in some use cases.

 

Outlook

Within the next article of this series I want to show the second and in my opinion the most important aspect of the Visitor pattern: the type specific dispatching of the container Elements.

Veröffentlicht unter C++ | Kommentar hinterlassen

The Visitor Pattern – part 1: basics and introduction

The Visitor Pattern is one of the base implementation patterns in software development. It can solve a base use case which occurs in nearly all applications which use inheritance to implement a system of base classes and derived classes. But unfortunately, the Visitor Pattern comes with a big disadvantage: it is one of the most misunderstood and misused patterns I know. Therefore, I want to spend some time to analyze the pattern, explain its strength, look for the reasons for the misunderstandings and show some ways to implement the pattern. As these are large topics I will split it up into several articles. Within this article we will start with the basic ideas of the pattern and have a look at the technical background. Within the next articles we will continue with concrete implementation examples for different use cases, remove the misunderstandings by finding the real meaning of the pattern, compare it with other patterns and have a look at the pros and cons of the Visitor.

 

Motivation

Collections are widely used data type structures. In object oriented systems, these collections often contain objects of different types, stored as pointers to a common base type. Algorithms or queries which perform operations based on these data collections will on one hand use the common base data interface and on the other hand need to use the type specific interface. Furthermore, the data collections may have very different kinds of complexity: list, tree or other structure. Independent of the data structure and independent whether the elements are stored as base class pointer, we want to iterator over elements and execute some algorithms based on these elements.

 

Reasons for Misunderstandings

Before we look at the Visitor Pattern I want to explain why there are is so many confusion and misunderstanding regarding this pattern.

Many of the software design patterns are easy to understand. The patterns itself and the involved classes and functions have clear names telling us what they are used for. The pattern implementations have a simple structure and they use well known language specific features.

But the Visitor pattern is different. It hasn’t a simple structure but is more complex. It hasn’t a clear naming. The name Visitor may arouse expectations which can be totally wrong. The interface name and the function names are meaningless too. And finally, it isn’t based on language specific features but it is a workaround for language specific limitations.

You can see the confusion about the Visitor pattern if you read a couple of articles and compare their statements. You will find very different statements about the purpose of the pattern, the use cases it solves and the reasons why you should use the pattern. Please, don’t get me wrong, these articles are not incorrect at all but they use the Visitor pattern in a not suitable context. There are many use cases which can be solved better with other patterns ore oven with standard language features.  In these cases, the Visitor pattern is over-engineering which results in some big disadvantages like less understandable and less maintainable source code with higher chance for errors.

Within this and the future articles I want to analyze and explain the Visitor pattern in detail to clarify and remove all the sources for misunderstandings.

 

Basics

After the long foreword and introduction, we will now start get to know the Visitor pattern and its implementation. At first, we will try to find a definition for this pattern. As mentioned before, you will find a lot of different explanations and definitions which are not contrary but may contain too much details and facts which are true in general but not specific for the Visitor pattern. Therefore, I want to give this short definition:

The Visitor design pattern is a way to traverse over all elements of a data container and get the type specific element instances. It separates the algorithms from the object structure on which they operate.

The definition mentions three important points: traverse elements, get type specific instance and separation of concerns.

Let’s compare this definition with the definition of the Iterator pattern: The Iterator pattern is a design pattern which uses an iterator to traverse a container and access the container’s elements. It separates the algorithms from the object structure on which they operate.

So, the Visitor pattern and the Iterator pattern are very similar. They will both separate algorithms from the data structure. Therefore, this separation of concerns isn’t a criterion to choose this pattern. But unfortunately, you will find a lot of articles which use this criterion as the reason to choose the Visitor pattern.

But there are also some differences between the two patterns. They have a different way to traverse the elements and they will return different element instances. The Iterator pattern returns the element as it is. If the data container holds elements as pointers to the common base class, the base class element pointer is returned. In contrast, the Visitor pattern will return the specific element type instance. Therefore, it will not return the base class pointer, but will return the specific derived types. The way to traverse the elements and the way to return the elements are the real characteristics of the Visitor pattern.

The Visitor pattern is implemented by using “double dispatch”. This technique allows to access the specific element types without any workaround like casting. To understand the pattern, we must understand this implementation technique. Therefore, we will continue with a short analysis and comparison of “single dispatch” and “double dispatch”.

 

Single Dispatch

The decision of which version of a method to call based on a single object is called single dispatch. This is the standard way of calling a function and it is directly supported by common object-oriented languages. When you call a regular virtual function, it is a single dispatch. The code that gets executed depends on the runtime type of a single object.

The following code shows an example application which uses single dispatch. We have date elements with common virtual and object specific non-virtual functions. The element instances will be stored within a container as a reference to the base object. Furthermore, we have a printer algorithm which loops over the container elements and calls the common virtual and object specific non-virtual function to print the outputs.

//------------------------------
// elements
//------------------------------

class BaseElement
{
public:
  virtual void DoSomething(){ std::cout << "DoSomething() of Base was called" << std::endl; }; 
};

class DerivedElementA : public BaseElement
{
public:
  virtual void DoSomething(){ std::cout << "DoSomething() of A was called" << std::endl; };

  std::string GetName() { return "element a"; }
};

class DerivedElementB : public BaseElement
{
public:
  virtual void DoSomething(){ std::cout << "DoSomething() of B was called" << std::endl; };

  std::string Name() { return "element b"; }
};

//------------------------------
// data container
//------------------------------

class DataContainer
{
public:
  std::vector mElements;
};

//------------------------------
// algorithm
//------------------------------

class Printer
{
public:
  void ShowAll(DataContainer& data)
  {
    for (const auto& element : data.mElements)
    {
      element->DoSomething();

      DerivedElementA* elementA = dynamic_cast(element);
      if (elementA)
      {
        std::cout <GetName() << std::endl;
      }

      DerivedElementB* elementB = dynamic_cast(element);
      if (elementB)
      {
        std::cout <Name() << std::endl;
      }
    }
  }
};

//------------------------------
// application
//------------------------------

int _tmain(int argc, _TCHAR* argv[])
{
  DerivedElementA* elementA = new DerivedElementA();
  DerivedElementB* elementB = new DerivedElementB();

  DataContainer container;
  container.mElements.push_back(elementA);
  container.mElements.push_back(elementB);

  Printer printer;
  printer.ShowAll(container);

  delete elementA;
  delete elementB;

	return 0;
}

 

The source code shows two examples for single dispatch function calls used to call the element functions. Based on the concrete instance we can call the object specific functions and based on the base class instance pointer we can call the virtual function and the according object specific function is executed.

C++ can do this run-time lookup dynamic dispatch for one type at a time, so it is a single dispatch. But c++ is not able to select a function based on two dynamic types, which would be a double dispatch. Another language specific limitation comes with the virtual function. We cannot add a virtual function to a class hierarchy without a modification of the base class which provides the interface.

Within the example we can see these limitations. It would not be possible to call the type specific functions of the derived classes without the use of any workaround, in our case the use of a dynamic cast. This workaround is necessary as c++ does not support double dispatch.

 

Double Dispatch

Double dispatch allows to call a method by a dynamic dispatch based on its runtime type. Furthermore, the decision of which version of a method is invoked based on a combination of two objects is also called double dispatch. Double dispatch is not directly supported by C++. Workaround for these limitations are dynamic casts, the Visitor pattern or the use of according third-party libraries.

We will now change the previous example a little bit. We will try to eliminate the need of the dynamic cast. This could be done if we implement an own double dispatch mechanism. Our goal is to get the runtime specific object by the base class pointer. So, we must add a virtual function to our base class to request the actual object instance. Each derived class can override the virtual function and returns its actual type specific instance. Furthermore, we need a receiver which can accept these different type specific instances. In the example, the receiver is the printer algorithm. Therefore, we will extend the printer with an interface to receive the object instances. And the element class will be extended by the virtual function to request the element instance.

//------------------------------
// forward declaration
//------------------------------

class DerivedElementA;
class DerivedElementB;

//------------------------------
// printer interface
//------------------------------

class IPrinter
{
public:
  virtual void ShowDerivedElement(DerivedElementA* elementA) = 0;
  virtual void ShowDerivedElement(DerivedElementB* elementB) = 0;  
};

//------------------------------
// elements
//------------------------------

class BaseElement
{
public:
  virtual void DoSomething(){ std::cout << "DoSomething() of Base was called" << std::endl; };

  virtual void GetElement(IPrinter* printer){};
};

class DerivedElementA : public BaseElement
{
public:
  virtual void DoSomething(){ std::cout << "DoSomething() of A was called" <ShowDerivedElement(this);
  };
};

class DerivedElementB : public BaseElement
{
public:
  virtual void DoSomething(){ std::cout << "DoSomething() of B was called" <ShowDerivedElement(this);
  };
};

//------------------------------
// data container
//------------------------------

class DataContainer
{
public:
  std::vector mElements;
};

The printer algorithm must implement the instance receiver functions and is now able to call the type specific element methods within these instance receivers.

//------------------------------
// algorithm
//------------------------------

class Printer : public IPrinter
{
public:
  void ShowAll(DataContainer& data)
  {
    for (const auto& element : data.mElements)
    {
      element->GetElement(this);
    }
  }

  void ShowDerivedElement(DerivedElementA* elementA)
  {
    elementA->DoSomething();
    std::cout <GetName() <DoSomething();
    std::cout <Name() << std::endl;
  }
};

//------------------------------
// application
//------------------------------

int _tmain(int argc, _TCHAR* argv[])
{
  DerivedElementA* elementA = new DerivedElementA();
  DerivedElementB* elementB = new DerivedElementB();

  DataContainer container;
  container.mElements.push_back(elementA);
  container.mElements.push_back(elementB);

  Printer printer;
  printer.ShowAll(container);

  delete elementA;
  delete elementB;

  return 0;
}

 

With this kind of implementation, we could successfully remove the dynamic cast. The algorithm instance has called an element method which has done a call to the algorithm method. We have executed two v-table calls to assign the operation to the data. The functionality is based on a combination of two objects and so we have implemented a double dispatch behavior.

Of course, you may ask whether it makes sense to implement such a complex system of mutual method calls just to have something which behaves like double dispatch. We could use single dispatch instead and will have the same results. This is a very important fact! Such a double dispatch implementation is always complex, creates additional dependencies and makes the source code harder to understand and to maintain. But on the other hand, it can add some benefit, like the elimination of the dynamic cast, which helps to eliminate errors. I would suggest using single dispatch as default implementation technique. You should use double dispatch in use cases only, were the implementation leads to benefits which exceed the disadvantages of the complex source code.

 

Definition

Within the “Basics” chapter we already seen a short definition for the Visitor pattern. Based on this definition and based on the double dispatch technique I want to explain the Visitor pattern in more detail.

The Visitor pattern defines an infrastructure to access a complex and dynamic data structure.  It implements an enumeration over the data structure elements independent of the structure type or complexity. To access each element a double dispatch technique is used which requests the specific element type instance and provides these instance to a receiver. This allows to implement algorithms based on the data structure. These algorithms will use the enumeration feature to traverse all elements and the algorithms implement the element instance receiver functions. So, they get access to the type specific instances of all elements.

The visitor pattern allows a separation of the data structure, the traversing over the structure and the algorithms based on the data. Furthermore, it allows to access the specific object independent whether it is stored as specific object or a pointer to any of the base classes. So, the data structure can hold elements without knowing their real types. Unfortunately, the pattern can only be implemented by using double dispatch. As we learned, double dispatch is based on a combination of two objects. This unavoidable adds a dependency between these two objects, in this case between the data elements and the algorithms. Later on, we will see the issues and disadvantages of this dependency.

 

Structure of the Pattern

The definition and explanation above already names the components of the Visitor pattern. These are:

  • Data Elements
  • Data Container
  • Enumerator
  • Algorithm

 

If you look at the Visitor pattern definition, done by the “Gang of Four” you will not find an “Enumerator” component. In my opinion, the pattern should not be used in use cases where you don’t need to enumerate over elements. There are much better patterns available in these cases. I will explain this in more detail later.

Many of the examples and definitions out there also respect this aspect and contain a traversing over the data elements. But most of them will not explicitly separate the algorithms from the traversing. But as these are very different concerns, they should be separated into two different components. Therefore, my big picture of the pattern explicitly contains the “Enumerator” component.

 

Naming

As mentioned before I don’t like the name of the pattern and its components and functions. They are not bad at all but I think they are to general. They are suitable to describe a generic concept like double dispatch but they are not sufficiently concrete to describe such a specific design pattern.

AS we have seen in the double dispatch chapter, a client will not use the element object directly. Instead it will call an element function which should “accept” the client as receiver for the element instance. Within this element function the element instance is send back to the client so the client can “visit” the specific element instance. Therefore, the visitor pattern defines these two methods: “accept” and “visit”. And according to this visiting thought it is called “Visitor”. But you see, even within this attempt for a serious explanation, the naming sounds strange.

Within the article and the code examples I want to use a clear naming. Of course, I will continue to call the pattern “Visitor pattern”. Furthermore, I want to call the interfaces according to this pattern. This will ensure an explicit relation with the pattern and I think the source code will be easier to understand if common names are used. So, I want to call the interface which must be implemented by the data elements “Visitable” and the interface of the algorithms and enumerators “Visitor”. The function to get the element instance well be called “GetInstance” and not “accept”. The function to transfer the instance to the instance receiver will be called “InstanceReceiver” and not “visit”. This function could also be called “ProcessInstance” or something in this way, but as we don’t know whether the algorithm reads, writes or even uses the element I prefer such a general name like “InstanceReceiver”.

 

Example application

Within the further progress of the article I want to introduce a simple example application. This application should show use cases of a simple shop system. It contains some articles which get stored in simple lists or even complex structures. And we want to create some queries to access and analyze the data. The example will therefore contain the components we have seen so far: elements, data containers, enumerators and algorithms. Within the example we will call these components: elements, containers, enumerators and queries.

 

Summary and Outlook

This first article about the Visitor pattern was thought as short introduction into this topic. But the resulting size of the article is a good indicator for the complexity of the pattern. So far, we got an overview about the use cases which could be solved by the pattern. We have analyzed the technical background needed to implement the pattern. And we build up a detailed background knowledge to understand the purpose of the Visitor. Based on this knowledge you will not belong to the group of developers which misunderstands the pattern and therefore don’t use it or use it in a wrong way.

Within the next articles we will expand our knowledge and see some concrete implementations of the pattern. These implementations will cover the different use cases like enumeration and dispatching and they will show different techniques to implement the pattern, like inheritance, composition or templates. Furthermore, we will analyze the pros and cons of the pattern to get a better feeling whether a use case can be solved by the pattern or not.

Veröffentlicht unter C++ | Kommentar hinterlassen