基于策略的设计 - 处理多种类型的理想方式,例如将它们存储在容器中,迭代等

Policy Based Design - Ideomatic way to deal with multitude of types, e.g. storing them in a container, iterating, etc

在来自 wikipedia 的基于策略的设计的 hello world 示例中,我们使用通用接口 HelloWorld 并通过模板使用不同的策略配置它 - 到目前为止一切顺利:

int main() {
  // Example 1
  typedef HelloWorld<OutputPolicyWriteToCout, LanguagePolicyEnglish>
      HelloWorldEnglish;

  HelloWorldEnglish hello_world;
  hello_world.Run();  // Prints "Hello, World!".

  // Example 2
  // Does the same, but uses another language policy.
  typedef HelloWorld<OutputPolicyWriteToCout, LanguagePolicyGerman>
      HelloWorldGerman;

  HelloWorldGerman hello_world2;
  hello_world2.Run();  // Prints "Hallo Welt!".
}

这一切都非常漂亮和优雅,但是管理/存储此类可配置对象集合的惯用方式是什么?例如,有人想写

std::vector< some_magic_type > seasons_greetings;  // What is the common type specifying the public interface only?
seasons_greetings.push_back(hello_world);  // which is of type HelloWorldEnglish
seasons_greetings.push_back(hello_world2); // which is of type HelloWorldGerman
for (greeting : seasons_greetings) {
  greeting.Run() // access only the public interface
}

在将接口设计为基础 classes 并从中派生专门的实现时,我没有这个问题 - 我总是可以存储指向基础 class 类型的指针 - 但我需要拼出导致大量派生 classes 的所有实现。 基于策略的设计承诺通过使用模板来混合和匹配行为来缓解随之而来的派生 classes 的爆炸式增长。但是我为此付出了很多不同的类型。

必须有一种惯用的方法来处理这个问题。非常感谢任何见解。

P.S。 我承认我没有买这本书,但你可能已经猜到了。 answer 建议存储集合意味着基于继承的设计,但真的如此吗?

如果您想与不相关的 类 保持联系,您可以使用 std:variant in combination with std::visit

示例(简单地扩展原始示例的主要功能)

    using Variant = std::variant< HelloWorld<OutputPolicyWriteToCout, LanguagePolicyEnglish>, HelloWorld<OutputPolicyWriteToCout, LanguagePolicyGerman> >;
    std::vector< Variant > seasons_greetings; 
    seasons_greetings.push_back(hello_world);
    seasons_greetings.push_back(hello_world2);
    for (auto& greeting : seasons_greetings) {
        std::visit( [](auto& what){ what.Run(); }, greeting );
    }   

不好的一面:你必须知道所有可能的政策组合,这真的很不舒服。但是您可以使用一些元模板的东西通过为每个使用的策略提供类型列表来创建所有变体类型,并且模板将生成所有组合,这并不是什么大问题。

所以这是我在再次思考评论后找到的解决方案 - 关键似乎结合了两全其美,多态性和政策 - 谁会想到....

通过引入仅定义行为 public 接口的公共基础 class,然后从中导出行为 class 和策略,我可以得到我想要的:

class HelloWorldInterface {
    public:
    // Behavior method.
    void Run() const {
        run();
    }
    private:
    virtual void run() const = 0;
};

template <typename OutputPolicy, typename LanguagePolicy>
class HelloWorld : public HelloWorldInterface, private OutputPolicy, private LanguagePolicy {
 private:
   using OutputPolicy::Print;
   using LanguagePolicy::Message;
   void run() const override final {
       Print(Message());
   }
};

然后,我基本上可以从头写出我想要的了:

int main() {
  // Example 1
  using HelloWorldEnglish =  HelloWorld<OutputPolicyWriteToCout, LanguagePolicyEnglish>;

  // Example 2
  // Does the same, but uses another language policy.
  using HelloWorldGerman =  HelloWorld<OutputPolicyWriteToCout, LanguagePolicyGerman>;

  HelloWorldEnglish hello_world;
  HelloWorldGerman hello_world2;

  std::vector<const HelloWorldInterface*> greetings{&hello_world, &hello_world2};

  for (auto x : greetings) {
      x -> Run();
  }
}

通过这种方式,人们可以获得一个单一的 public 界面,该界面可以公开由任意策略组合产生的行为。不过现在看起来微不足道。