管理不同组件实现的控制器构造的设计问题

Design issues with a controller construct managing different component implementations

我正在思考一个相对简单的问题,但似乎找不到解决这个问题的方法 - 甚至有点老套...,我只是被卡住了,可以使用一些似乎不会流行的想法向上。下面显示的代码示例没有(完全)工作,而是用来显示我的问题。

我有一个控制器 class,有 3 种方法 initprocessdispose。控制器基本上是一个或多个要执行的所谓组件的包装器。

此外,还有一个 component_manager class 来创建组件实例并管理它们的生命周期。组件管理器派生自定义了 4 个虚方法的接口,如 initpreparerecycledispose

组件本身也有一个定义良好的接口icomponent。所有组件都派生自该接口,并且所有组件都是默认可构造的(不需要参数)。

为了展示一些代码,我从我正在试验的代码中提取了重要部分,省略了“控制器”包装器 class 及其周围的所有噪音。

namespace detail {

// Iterator for std::tuple    
template<typename Tuple, typename F, std::size_t ...Indices>
constexpr void for_each_impl(Tuple&& tuple, F&& f, std::index_sequence<Indices...>) 
{
    using swallow = int[];
    (void)swallow{1,
        (f(std::get<Indices>(std::forward<Tuple>(tuple))), void(), int{})...
    };
}

} // Namespace detail

template<typename Tuple, typename F>
constexpr void for_each(Tuple&& tuple, F&& f) 
{
    constexpr std::size_t N = std::tuple_size<std::remove_reference_t<Tuple>>::value;
    detail::for_each_impl(std::forward<Tuple>(tuple), std::forward<F>(f),
        std::make_index_sequence<N>{});
} 

// A component
class icomponent {
public:
    virtual ~icomponent() = default;
    virtual std::string id() = 0; 
};

// Sample components a, b and c
class component_a : public icomponent { 
public:    
    virtual std::string id() override { return "component a"; } 
};
class component_b : public icomponent { 
public:    
    virtual std::string id() override { return "component b"; } 
};
class component_c : public icomponent { 
public:    
    virtual std::string id() override { return "component c"; } 
};

// Interface defining a component manager
class iprocessing_component_manager
{
public:
    virtual ~iprocessing_component_manager() = default;

    virtual T* prepare() = 0;

    // More members like init(), recycle() and dispose()
};

// An simple, possible implementation of component manager
class simple_processing_component_manager 
    : public iprocessing_component_manager
{
public:
    virtual ~simple_processing_component_manager() = default;

    // ... other implemented methods ...

    virtual T* prepare()
    {
        // ... creation of component
    }
};

// In the controller wrapper, the process method
template<typename ...T>
void process()
{
    // This initialization is normally within the controller wrapper
    std::unique_ptr<simple_processing_component_manager> component_mgr = std::make_unique<simple_processing_component_manager>();

    // Convert to tuple
    std::tuple<T...> components;

    // Iterate over (component) types ...
    for_each(components, [&](auto& comp) {  

       using component_t = typename std::remove_reference<decltype(comp)>::type;

       // Steps would be ...

       // Instantiate the component (doesn't work, just showing what I need to accomplish)
       component_t* x = component_mgr->prepare<component_t>();
       //                                     ^^^^^^^^^^^^^

       // Perform component execution ... here I just print the id of the component
       std::cout << x->id() << "\n";

       // After processing component_mgr recycle() and / or dispose() are called
       delete x;
    });
}

将它们放在一起,典型的控制器用法如下所示:

controller c(/* some initialization parameters */);
result = c->process<component_a, component_c>(/* some more parameters */);

这将是我的首选使用方式,将组件作为可变模板参数传递。在控制器 process 方法中,这些组件类型被迭代和执行。我真的很想用 process 方法而不是控制器实例化来传递组件。

我的基本问题依赖于组件管理器 prepare 方法。 component_manager 及其 prepare 方法的简单实现如下所示:

T* prepare(/*...*/)
{
    return new T(); // Create component instance
}

但是,我会有不同的组件管理器实现,例如,我想通过使用对象池来回收和重用创建的组件指针。在这种情况下,我可能会有

return borrow_object<T>(/* some key */); // Get from pool, will be returned back to pool with `recycle`...

A 最后,我将有各种不同的 component_managers,并且 prepare 和其他 recycle/disposal 方法的每个实现都会有所不同,但是,对于所有情况,我都需要组件类型 T。但是......模板方法不能是虚拟的。我知道这一点!

有什么方法可以解决上述问题,或者有更好的方法或设计来解决这个问题吗?我坐着盯着看这个太久了,所以如果我错过了一些非常简单的事情,请多多包涵。

感谢您的宝贵时间!

我不是继承和虚拟方法方面的专家...我不确定是否理解您的确切要求...无论如何,正如您所说,模板方法不能是虚拟的。

所以你必须考虑这个。

但是模板方法可以调用模板中的虚方法class。

所以我建议将 iprocessing_component_manager 中的虚拟 prepare() 设为 return 一个 icomponent 指针。

struct iprocessing_component_manager
 {
   virtual ~iprocessing_component_manager () = default;

   virtual icomponent * prepare () = 0;
 };

接下来,准备一个派生的单一类型模板 class 实现 prepare()

template <typename T>
struct type_processing_component_manager
   : public iprocessing_component_manager
 {
   virtual ~type_processing_component_manager () = default;

   virtual T* prepare () override
    { return new T; }
 };

注意 prepare() return 是指向 T 的指针,而不是指向 icomponent.

的指针

现在您可以编写一个可变参数模板 tuple_processing_component_manager,它继承自所有 type_processing_component_manager<Ts>,并使用调用正确的模板(显然不是 virtualprepare() (取决于类型)虚拟继承 prepare() 方法。

template <typename ... Ts>
struct tuple_processing_component_manager
   : public type_processing_component_manager<Ts>...
 {
   virtual ~tuple_processing_component_manager () = default;

   template <typename T>
   T * prepare ()
    { return type_processing_component_manager<T>::prepare(); }
 };

在主要 process() 中,您可以 component_mgr 基于可变处理组件管理器

auto component_mgr
 { std::make_unique<tuple_processing_component_manager<Ts...>>() };

并在 lambda 中使用它,如下所示

  using component_t
     = typename std::remove_reference<decltype(comp)>::type;

  std::unique_ptr<component_t> x
   { component_mgr->template prepare<component_t>() };

  std::cout << x->id() << "\n";

以下是完整的工作示例。

#include <tuple>
#include <memory>
#include <utility>
#include <iostream>

namespace detail
 {
   template <typename Tuple, typename F, std::size_t ... Is>
   constexpr void for_each_impl (Tuple && tuple, F && f,
                                 std::index_sequence<Is...>) 
    {
      using swallow = int[];

      (void)swallow { 1,
         ((void)f(std::get<Is>(std::forward<Tuple>(tuple))), 0)... };
    }
 }

template <typename Tuple, typename F>
constexpr void for_each (Tuple && tuple, F && f) 
 {
   constexpr auto N 
    { std::tuple_size<std::remove_reference_t<Tuple>>::value };

   detail::for_each_impl(std::forward<Tuple>(tuple), std::forward<F>(f),
                         std::make_index_sequence<N>{});
 } 

struct icomponent
 {
   virtual ~icomponent () = default;

   virtual std::string id () = 0; 
 };

struct component_a : public icomponent
 { virtual std::string id () override { return "component a"; } };

struct component_b : public icomponent
 { virtual std::string id () override { return "component b"; } };

struct component_c : public icomponent
 { virtual std::string id () override { return "component c"; } };

struct iprocessing_component_manager
 {
   virtual ~iprocessing_component_manager () = default;

   virtual icomponent * prepare () = 0;
 };

template <typename T>
struct type_processing_component_manager
   : public iprocessing_component_manager
 {
   virtual ~type_processing_component_manager () = default;

   virtual T* prepare () override
    { return new T; }
 };

template <typename ... Ts>
struct tuple_processing_component_manager
   : public type_processing_component_manager<Ts>...
 {
   virtual ~tuple_processing_component_manager () = default;

   template <typename T>
   T * prepare ()
    { return type_processing_component_manager<T>::prepare(); }
 };

template <typename ... Ts>
void process ()
 {
   auto component_mgr
    { std::make_unique<tuple_processing_component_manager<Ts...>>() };

   std::tuple<Ts...> components;

   for_each(components, [&](auto& comp)
    {  
      using component_t
         = typename std::remove_reference<decltype(comp)>::type;

      std::unique_ptr<component_t> x
       { component_mgr->template prepare<component_t>() };

      std::cout << x->id() << "\n";
    });
}

int main ()
 {
   process<component_a, component_b, component_c>();
 }

如果你对componentsstd::tuple不感兴趣,在for_each()处理中,你可以直接在递归[=]中使用type_processing_component_manager结构29=]

// ground case
template <int = 0>
void process ()
 { }

// recursive case
template <typename T0, typename ... Ts>
void process ()
 {
   type_processing_component_manager<T0> component_mgr;

   std::unique_ptr<T0> x { component_mgr.prepare() };

   std::cout << x->id() << "\n";

   // recursion
   process<Ts...>();
 }