通用策略模式






4.79/5 (8投票s)
用于在 C++ 中创建可定制模板库的模式
动机
策略和特性驱动的设计是 C++ 模板元编程中一种非常强大且常用的实践,但目前它存在一些严重的缺陷。
尽管通过应用不同的策略和特性来定制库类很容易,但要定制策略和特性本身却非常困难。
首先,向现有且广泛使用的类添加新策略几乎是不可能的。让我们以标准模板库 (STL) 为例。向任何 STL 容器添加多线程策略几乎是不可能的,因为许多现有代码都假定 std::vector
只有两个模板参数——值类型和分配策略。此外,如果任何基于 STL 的代码尚未(例如 std::exception
)使用分配策略,它仍然依赖于 STL 定义的默认分配策略,而无法开始使用新的多线程策略,并且无法对其进行硬编码。
此外,管理策略之间的依赖关系(参数化类的某个策略可能需要访问用于定制目标类的其他策略)也非常困难。管理依赖关系的最简单方法是在每次使用时对其进行硬编码,并用另一个策略定制一个策略,但这可能不是一种灵活的方法。一套策略的不同实现可能需要一套不同的策略依赖关系。
因此,我们在实现模板库时不对类型进行硬编码,并允许通过策略定制此类实现,但我们硬编码了可能的策略列表(并且以后无法向此列表添加新策略)以及此类策略实现之间允许的依赖关系。
本文的目标是解决这些缺陷,并使在设计 C++ 模板库时更容易利用 C++ 模板元编程。
问题示例
Loki 库 [2] 中的 SmartPtr
类中可以找到策略之间硬编码依赖关系的示例。该类的声明如下:
template
<
typename T,
class OwnershipPolicy,
class ConversionPolicy,
class CheckingPolicy,
class StoragePolicy
>
class SmartPtr
: public StoragePolicy::In<T>::type
, public OwnershipPolicy::In<typename StoragePolicy::template PointerType<T>::type>::type
, public CheckingPolicy::In<typename StoragePolicy::template StoredType<T>::type>::type
, public ConversionPolicy
{/* ... class body */};
在此示例中,任何 OwnershipPolicy
和 CheckingPolicy
的实现都必须依赖于 StoragePolicy
。例如,不可能创建依赖于 CheckingPolicy
的 StoragePolicy
实现。
这些问题之所以发生,是因为我们通过单独的模板参数将所有策略传递给目标类。每个策略都通过一个可能具有默认值的专用参数传递。这会产生另一个问题,因为我们需要在传递所有具有默认值的参数之前,传递所有没有默认值的参数。
解决方案概览
初步方法
处理此问题的简单尝试是通过一个模板参数——通用策略——将所有策略和特性传递给泛型库的所有类。在这种情况下,既不硬编码指定的策略列表,也不硬编码策略之间的依赖关系。模板库仅通过提供 default_general_policy
类来组织通用策略的结构。用户可以继承该类并仅覆盖感兴趣的策略。这样,模板库维护者就可以随时添加新的策略及其默认值。模板库的所有类都将只接收一个具有默认值 default_general_policy
的通用策略模板参数。可以在 default_general_policy
中更改任何默认值,从而保持库的向后兼容性,用户将自动开始使用新的默认值。
这种通用策略的另一个好处是,泛型库的用户可以确保所有代码都为所有目的使用相同的策略集,并且可以在一个地方更改策略集,而无需修改整个代码库。
使用通用策略模式的最简单示例可能如下所示:
#include <iostream>
#include <string>
namespace provided_library {
// Library provide default set of policies.
// Any number of policies can be added here during period
// of lifetime of generic library.
class default_general_policy {
public:
//Example of outputing policy.
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
// Example of some class in generic library
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
// user redefines some aspects of general policy
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;
}
改进的方法
在上面的示例中,worker 类能够使用 general_policy
中的任何子策略。default_general_policy
类不仅提供默认值,还提供通用策略的结构。worker 类期望一个与 default_general_policy
具有相同结构的模板参数。用户必须继承 default_general_policy
以确保策略结构保持不变,但覆盖用户定义的策略类型。
在新版本的库中,库提供者可以在 default_general_policy
中添加 threading_model_t
策略,并在所有类(如 worker)中使用它。向后兼容性得到保留,旧的用户代码可以与新版本的库一起编译。所有库类都将使用默认的 threading_model_t
。用户仍然可以在一个地方覆盖 threading_model_t
——即 user_general_policy
类的定义。一旦在 user_general_policy
中覆盖,所有用户代码都将开始使用新的线程模型。
此示例仍有一个缺陷。如果我们覆盖 user_general_policy
中的 output_t
,auto_logging_t
的默认实现仍将使用默认的 output_t
,而不是用户定义的。这种行为与我们想要实现的目标不一致。
解决方案是使 auto_logging_t
成为一个模板类,接受通用策略作为参数。它将在其模板参数中接收整个通用策略的实现,并且能够使用所有用户定义的类型,而无需指定实现必须依赖于哪些确切的策略。
正确的实现如下所示:
#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 here auto_logging_t becomes
// template because in depends from other policies.
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 << " non-default part of 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;
}
为了使每个单独的策略都可以覆盖,可定制的策略必须使用其模板参数来访问通用策略,类似于上面示例中 auto_logging_t
的默认实现。此类可定制策略不得直接使用 user_general_policy
类,以便用户可以继承 user_general_policy
并仅覆盖其中的某些策略,从而影响所有其他策略。反之,如果我们在覆盖的 auto_logging_t
类中使用 user_general_policy
而不是模板参数,那么很难找到问题,因为 user_general_policy
类在有人继承它之前都会正常工作。
策略特化
在某些情况下,有必要覆盖 user_general_policy
中的一个或多个策略,以便在特定位置使用它。为所有此类情况创建一组新的重新定义的策略将是繁琐且容易出错的。例如,一个类可能封装配置文件的读取,并且必须使用短整型作为字符类型,而不是char在用户通用策略中定义。用户代码库中可能存在大量此类情况,并且每种情况都可能需要重新定义一组不同的策略。
可以通过策略特化器来解决这种情况。策略特化器是一个模板类,它在模板参数中接收一个通用策略,并覆盖其中的一个方面。上面示例中输出策略的特化器可能如下所示:
template<typename general_policy_t>
class output_specializator : public general_policy_t {
public:
class output_t {
public:
static std::ostream& output_stream() {
std::cout << " non-default part of message. ";
return std::cout;
};
}; // class auto_logging_t
}; // class output_specializator
typedef provided_library::default_general_policy user_general_policy;
// ...
provided_library::worker< output_specializator<user_general_policy> >::do_work();
此外,库可以为每个策略提供默认值作为特化器,并使用单个 typedef
将它们全部组合成一个通用策略。
可参数化库
通用策略库的所有类都必须使用单个通用策略模板参数进行参数化。将整个库实现为单个参数化通用策略的类可能会很方便。在这种情况下,不必将整个库定义在一个文件中;可以通过使用 typedef
添加子类来构建此类:
template<typename general_policy>
class some_simple_class {/* ... body of the simple class ... */};
// template class has to have template_parameter after
// it will be parametrized with general_policy
template<typename general_policy, typename template_parameter>
class some_template_class {/* ... body of the template class ... */};
template<typename general_policy = default_general_policy>
class provided_library { // provided_library is class now
public:
typedef general_policy general_policy_t;
typedef ::some_simple_class<general_policy_t> some_simple_class;
// It is impossible to make alias name (typedef) for template,
// so it must be wrapped in a template class instead.
template<typename template_parameter>
class some_template_class {
public:
typedef ::some_template_class<general_policy_t,
template_parameter> result;
}; // class some_template_class
}; // class provided_library
// ... Ussage:
typedef provided_library<user_general_policy> customized_library;
// ...
customized_library::some_simple_class simple_worker_object;
typedef customized_library::some_template_class<int> customized_template_worker;
typename customized_template_worker::result template_worker_object;
参考文献
- David Vandevoorde, Nicolai M. Josuttis, C++ Templates: The Complete Guide, 2002.
- Andrei Alexandrescu, Modern C++ Design: Generic Programming and Design Patterns Applied, 2001.
历史
- 2007年2月1日 - 初始修订
- 2020年7月22日 - 语法更正