Применение основанного на политике вопроса проектирования
Я не читал книгу Modern C++ Design, но мне показалась интересной идея внедрения поведения через шаблоны. Я сейчас пытаюсь применить это сам.
У меня есть класс, в котором есть логгер, который, как я думал, может быть введен как политика. В логгере есть метод log(), который принимает std:: string или std:: wstring в зависимости от своей политики:
// 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;
};
Теперь вопрос состоит в том, должен ли читатель принять Logger в качестве аргумента, как описано выше, или он должен взять String, а затем создать basic_logger в качестве переменной экземпляра? Вот так:
template<class String>
class reader
{
public:
typedef String string_type;
typedef basic_logger<string_type> logger_type;
// ...
private:
logger_type _logger;
};
Какой правильный путь?
3 ответа
Чтобы фактически использовать класс политики, политика должна быть параметром шаблона. Одним из примеров является параметр char_traits для basic_string, хотя он реализован не так, как политики MC++D, в которых наследование используется для оптимизации пустого базового класса и для простого добавления в открытый интерфейс класса (намного лучше, чем оборачивая каждый возможный метод, снова прочитайте MC++D). Вы по-прежнему можете указать значение по умолчанию:
template<class String, class Logger=basic_logger<String> >
struct reader : Logger {
void read() {
this->log("Reading...");
}
};
Вопрос в том, должен ли читатель быть параметризован по типу его регистратора или по типу того, что он читает? Если бы это был вопрос, я бы подумал, что ответ очевиден - последнее.
Проблема с этим вопросом ИМХО в том, что ни String, ни Logger на самом деле не являются политиками. Политика говорит во время компиляции, как что-то вроде логгера должно идти о регистрации - ваш код просто предоставляет читателю тип логгера, который можно было бы сделать во время выполнения с использованием наследования.
Политики обычно являются параметрами, которые влияют на поведение класса.
Фактически довольно трудно извлечь политики из класса, и более сложным является то, что политики должны охватывать ортогональные концепции, чтобы вы могли изменить одно, не влияя на другие... что довольно сложно, как вы можете себе представить.
Если вы хотите увидеть хороший пример использования Политики, посмотрите вверх Loki::Singleton
что полностью продемонстрировано в книге.
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;
Впечатляет, не правда ли?
Принципы разработки на основе политик заключаются в том, что вы пытаетесь разложить различные действия вашего класса, чтобы вы могли самостоятельно обдумывать их.
Что ж, теперь я должен признать, что меня не устраивает идея требовать класса с заданным количеством параметров шаблона, я лично предпочел бы кое-что вместе:
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;
С последним ничего не поделаешь, ты должен пройти SingletonHolder
сам класс.
Однако я считаю, что здесь проще обмениваться политиками, это позволяет мне определять такие политики, как:
template <class T, size_t Param> MyCreationPolicy;
И использовать напрямую, без необходимости оборачивать его для заданного значения param, чтобы оно соответствовало сигнатуре.