Applying policy based design question
I've not read the Modern C++ Design book but have found the idea of behavior injection through templates interesting. I am now trying to apply it myself.
I have a class that has a logger that I thought could be injected as a policy. The logger has a log() method which takes an std::string or std::wstring depen开发者_运维百科ding on its policy:
// basic_logger.hpp
template<class String>
class basic_logger
{
public:
typedef String string_type;
void log(const string_type & s) { ... }
};
typedef basic_logger<std::string> logger;
typedef basic_logger<std::wstring> wlogger;
// reader.hpp
template<class Logger = logger>
class reader
{
public:
typedef Logger logger_type;
void read()
{
_logger.log("Reading...");
}
private:
logger_type _logger;
};
Now the questing is, should the reader take a Logger as an argument, like above, or should it take a String and then instantiate a basic_logger as an instance variable? Like so:
template<class String>
class reader
{
public:
typedef String string_type;
typedef basic_logger<string_type> logger_type;
// ...
private:
logger_type _logger;
};
What is the right way to go?
To actually be using a policy class, the policy needs to be a template parameter. One example is the char_traits parameter to basic_string, even though that's implemented differently than MC++D's policies, which use inheritance to make use of the empty base class optimization and to allow easy addition to a class's public interface (in a much better way than wrapping each possible method; again, read MC++D). You can still provide a default:
template<class String, class Logger=basic_logger<String> >
struct reader : Logger {
void read() {
this->log("Reading...");
}
};
Is the question should the reader be parameterised on the type of its logger, or on the type of the thing it reads? If this is the question, I would have thought the answer was obvious - the latter.
The problem with this question IMHO is that neither String nor Logger are actually policies. A policy says at compile-time, how something like a logger should go about logging - your code just supplies a type of logger to the reader, which could equally well have been done at run-time, using inheritance.
Policies are normally parameters that affect the class behavior.
It is effectively quite difficult to extract policies from a class, and the more difficult bit is that policies need to cover orthogonal concepts so that you can change one without impacting the others... which is quite challenging as you can imagine.
If you want to see a good example of Policy use, look up Loki::Singleton
which is entirely demonstrated in the Book.
template
<
typename T,
template <class> class CreationPolicy = CreateUsingNew,
template <class> class LifetimePolicy = DefaultLifetime,
template <class, class> class ThreadingModel = LOKI_DEFAULT_THREADING_NO_OBJ_LEVEL,
class MutexPolicy = LOKI_DEFAULT_MUTEX
>
class SingletonHolder;
Impressive, isn't it ?
The principles of Policy-based design is that you try and decompose the various action of your class so that you can reason about them independently.
Well, now I must admit I am not quite confortable with the idea of requiring class with a set number of template parameters, I personally would have prefered something along:
template
<
class T,
class CreationPolicy = CreateUsingNew<T>,
class LifetimePolicy = DefaultLifeTime<T>,
class MutexPolicy = LOKI_DEFAULT_MUTEX,
template <class, class> class ThreadingModel = LOKI_DEFAULT_THREADING_NO_OBJ_LEVEL
>
class SingletonHolder;
The last cannot really be helped, you're supposed to pass it the SingletonHolder
class itself.
However I find it easier to interchange policies here, it allows me to define policies like:
template <class T, size_t Param> MyCreationPolicy;
And use directly without having to wrap it for a given value of param so that it matches the signature.
精彩评论