Topic: General Policy Pattern.


Author: voidbent@gmail.com
Date: Wed, 22 Feb 2006 09:33:52 CST
Raw View
I would like to introduce my idea about general policy pattern. It
think it can be very useful while designing TR2 (or any generic library
as well). Also I would like to receive ones feedback with opinion about
this idea. I am going to implement unit testing library based on
general policy to show all power of this pattern. So I have one more
question, if TR2 is going to have unit testing?

Full text of General Policy Pattern is here :

General Policy Pattern.

Policy and traits based design is very powerful and commonly used
practice in generic programming. But this practice has several serious
problems. User is able to manage library classes by applying different
policies, but it is very hard to manage policies itself. At first, it
is practically impossible to add new policy to existing and widely used
class. Event if STL authors want to add threading models to STL
containers they cannot do that because a lot of code supposes that
std::vector has only two template parameters - value type and
allocation policy. Moreover, if any STL class doesn't use allocation
policy it cannot start using this standard policy. Also, it is very
hard to manage dependencies between policies. The easiest way to manage
dependencies is to hardcode it and customizes one policy with other,
but it is not flexible. Different policies set may require different
set of dependencies.
Now the situation with policies looks like we don't hardcode policy
types, which our library class will use but we do hardcode both list of
possible policy types and dependencies between those types.
One can say that all those problems with policies happen because we
pass policies to class of library through template parameters. Each
policy is passed through its parameter and this parameter may have
default value. There is one more problem with passing default values
before non-default ones. With passing default values we have not less
problems than with policies itself.
So, obvious and simple possible solution of all those problems is to
pass all policies and traits to all classes of our library through one
template parameter - General Policy. Indeed, we will hardcode neither
list of necessary policies nor dependencies. Library will only organize
structure of general policy by providing default_general_policy class.
User will be able to inherit this class and override some of policies.
So we will be able to add new policies to default_general_policy
latter. All classes of library will receive one general policy template
argument with default value default_general_policy. So that any
defaults can be changed in default_general_policy and user will start
use new defaults automatically. One more benefit of General Policy
Pattern consist in fact that user of generic library is able to be sure
that all users code use one set of policies for all purposes and one
can easy change this set without overwriting all code. User has to
change one policy in one place and all code start using this new
implementation policy, unlike classic solution where user has to review
and rewrite all code to be sure that new policy is used everywhere.

Example of using of this pattern may look like this:

#include <iostream>
#include <string>

namespace provided_library {

   class default_general_policy {
   public:

      class output_t {
      public:
         static std::ostream& output_stream() {
            return std::cout;
         };
      }; // class output_t

      class auto_logging_t {
      public:
         auto_logging_t(const std::string& method_name)
            : _method_name(method_name)
         {
            output_t::output_stream() << "Started Method : "
               << _method_name << std::endl;
         }

         ~auto_logging_t()
         {
            output_t::output_stream() << "Finished Method : "
               << _method_name << std::endl;
         }

      private:
         std::string _method_name;
      }; // class auto_logging_t

   }; // class default_general_policy

   template<typename general_policy = default_general_policy>
   class worker {
   public:
      typedef general_policy general_policy_t;
      typedef typename general_policy_t::auto_logging_t auto_logging_t;
      typedef typename general_policy_t::output_t output_t;

      static void do_work() {
         auto_logging_t do_logging("worker::do_work");
         output_t::output_stream() << "Worker is working ..." <<
std::endl;
      }
   }; // class worker

   // ...
   // not only worker but also all classes of provided_library use one
   // general_policy instead of set of policies.

}; // namespace provided_library

class user_general_policy : public
provided_library::default_general_policy {
public:
   class auto_logging_t {
   public:
      auto_logging_t(const std::string& method_name)
         : _method_name(method_name)
      {
         output_t::output_stream() << "User Logging Method : "
            << _method_name << std::endl;
      }

      ~auto_logging_t()
      {
         output_t::output_stream() << "User Logging Method : "
            << _method_name << std::endl;
      }

   private:
      std::string _method_name;
   }; // class auto_logging_t
}; // class user_general_policy

int main() {
   provided_library::worker<>::do_work();
   std::cout << std::endl;
   provided_library::worker<user_general_policy>::do_work();
   return 0;
}


In this example class worker is able to use any policy from general
one. Class default_general_policy provide not only defaults itself but
also structure of general policy and worker will expect class with the
same structure as default_general_policy has. User has to inherit
default_general_policy to be sure that this structure will always be
the same, and override types he needs. Thus, author of library is able
to add threading_model_t for example to default_general_policy in new
version of library and uses it in all classes like worker. Old user
code will be able to be compiled with this new version of library and
all library classes will use default threading_model_t. User is able to
override threading_model_t in one place - definition of
user_general_policy, and all users code will start using new threading
model.

This example is simple enough, but it still contains one problem. If we
override output_t in user_general_policy, default implementation of
auto_logging_t will still use default output_t instead of overrided
one. I doubt that we want such behavior.
The solution is to make auto_logging_t sub-policy template class. It
will recursively receive implementation of whole general policy and
will be able to use all overrided types. And our fixed example may look
like this:

#include <iostream>
#include <string>

namespace provided_library {

   class default_general_policy {
   public:

      class output_t {
      public:
         static std::ostream& output_stream() {
            return std::cout;
         };
      }; // class output_t

      // Note that auto_logging_t becomes template.
      template<typename general_policy>
      class auto_logging_t {
         typedef typename general_policy::output_t overrided_output;
      public:
         auto_logging_t(const std::string& method_name)
            : _method_name(method_name)
         {
            overrided_output::output_stream() << "Started Method : "
               << _method_name << std::endl;
         }

         ~auto_logging_t()
         {
            overrided_output::output_stream() << "Finished Method : "
               << _method_name << std::endl;
         }

      private:
         std::string _method_name;
      }; // class auto_logging_t

   }; // class default_general_policy

   template<typename general_policy = default_general_policy>
   class worker {
   public:
      typedef general_policy general_policy_t;
      typedef typename general_policy_t::template
auto_logging_t<general_policy_t> auto_logging_t;
      typedef typename general_policy_t::output_t output_t;

      static void do_work() {
         auto_logging_t do_logging("worker::do_work");
         output_t::output_stream() << "Worker is working ..." <<
std::endl;
      }

   }; // class worker

}; // namespace provided_library

class user_general_policy : public
provided_library::default_general_policy {
public:

   class output_t {
   public:
         static std::ostream& output_stream() {
            std::cout << "message : ";
            return std::cout;
         };
   }; // class auto_logging_t

}; // class user_general_policy

int main() {
   provided_library::worker<>::do_work();
   std::cout << std::endl;
   provided_library::worker<user_general_policy>::do_work();
   return 0;
}

One has to note that if user wants to override such template
sub-policy, this sub-policy must use its template parameter like
default auto_logging_t do in example, but not to use
user_general_policy class directly. So that one will be able to inherit
such user_general_policy and override some other policies. Opposite, if
one will use user_general_policy in overrided auto_logging_t template
instead of template parameter it will be hard to find such problem
because behavior of user_general_policy will look correct and problem
becomes apparent only after anyone will inherit user_general_policy.

Now it is easy to add new policies to our library and manage its
dependencies, its easy to use one set of policies through all code and
it is easy to change this set. And problem with default values does not
exist any more. General Policy Pattern can improve policy and traits
based design practice and increase scalability of generic libraries.

References.

[1]. C++ Templates: The Complete Guide, by David Vandevoorde, Nicolai
M. Josuttis.
[2]. Modern C++ Design: Generic Programming and Design Patterns
Applied, by Andrei Alexandrescu.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]