【问题标题】:Good design for a reusable class with many derived classes具有许多派生类的可重用类的良好设计
【发布时间】:2018-07-31 10:19:22
【问题描述】:

我有一个类将作为(许多)其他类的基类。每个派生类的逻辑都围绕单个函数略有不同,该函数本身将是一组外部函数中的一个。我的目标是有一些高效、清晰的东西,并且会导致每个新派生类的附加代码量最少:

这是我想出的:

// ctor omitted for brevity

class Base
{
public:
    void process(batch_t &batch)
    {
        if (previous) previous->process(batch);
        pre_process(batch);
        proc.process(batch);
        post_process(batch);
    }

protected:
    // no op unless overridden
    virtual void pre_process(batch_t &batch) {}
    virtual void post_process(batch_t &batch) {}

    Processor proc;
    Base* previous;
}
  • 公开遵循设定模式的“流程”功能
  • 函数的核心逻辑由“处理器”类中的一个拖放定义
  • 允许通过两个虚函数修改此模式,这两个虚函数定义了在调用 Processor::proc 之前/之后完成的额外工作
  • 有时,这个对象有一个指向另一个必须在它之前做其他事情的句柄,为此我有一个指针'previous'

这个设计看起来不错还是有一些我没有考虑到的明显漏洞?或者在这种情况下还有其他常见的模式吗?

【问题讨论】:

  • 如果您不在任何地方使用DERIVED,为什么要使用 CRTP?
  • 我会在其他地方做,我只是发布了一个与扩展流程功能问题相关的班级的最小示例
  • 您应该删除 CRTP,或者修改示例以向我们展示它是如何使用的。我不认为我们可以为这个问题添加任何有用的东西。
  • 每个Base<Foo> 都是一个不同的类。您不能将Base<Foo1>Base<Foo2> 绑定到同一个引用或指针。那么 CRTP 真正服务的目的是什么?我认为 geza 是对的。
  • 啊,我明白了,在这种情况下,我可以废弃 CRTP 并只使用 vtable。我实际上希望 Processor::proc 调用无论如何都会主导额外的开销。忽略 CRTP 部分,其余的设计看起来是否合理?

标签: c++ oop design-patterns polymorphism


【解决方案1】:

这个设计看起来不错还是有一些我没有考虑到的明显漏洞?或者在这种情况下还有其他常见的模式吗?

在不了解您的目标的情况下,我只能说这似乎很明智。太懂事了,其实这个成语有一个通用的名字:A"Non-virtual Interface"。如果您在 Java 领域,也被四人组描述为 "Template Method Design Pattern"

【讨论】:

    【解决方案2】:

    您当前正在使用所谓的“模板方法”模式(例如,参见here)。您必须注意,它使用继承本质上通过覆盖pre_processpost_process 方法来修改process(batch) 函数的行为。这会产生强耦合。例如,如果您将基类子类化以使用特定的pre_process 实现,那么您不能在不复制代码的情况下在任何其他子类中使用此实现。

    我个人会采用“策略”模式(例如,参见here),它更灵活,更容易重复使用代码,如下所示:

    struct PreProcessor {
        virtual void process(batch&) = 0;
    };
    
    struct PostProcessor {
        virtual void process(batch&) = 0;
    };
    
    class Base {
    
    public:
        //ctor taking pointers to subclasses of PreProcessor and PostProcessor
    
        void process(batch_t &batch)
        {
            if (previous) previous->process(batch);
            pre_proc->process(batch);
            proc.process(batch);
            post_proc->process(batch);
        }
    
    private:
    
        PreProcessor* pre_proc;
        Processor proc;
        PostProcessor* post_proc;
        Base* previous;
    }
    

    现在,您可以创建 PreProcessorPostProcessor 的子类,您可以将它们混合搭配,然后传递给您的 Base 类。您当然可以对 Processor 类应用相同的方法。

    【讨论】:

      【解决方案3】:

      根据您的信息,我认为在这里使用继承(一个基类和许多派生类)没有任何好处。仅仅因为您有一些新的前/后处理逻辑而编写一个新的(整个)类并不是一个好主意。更不用说,这将使这些逻辑难以重用。

      我推荐一个更可组合的设计:

      typedef void (*Handle)(batch_t&);
      
      class Foo
      {
      public:
          Foo(Handle pre, Handle post, Foo* previous) :
              m_pre(pre),
              m_post(post),
              m_previous(previous) {}
      
          void process(batch_t& batch)
          {
              if (m_previous) m_previous->process(batch);
              (*m_pre)(batch);
              m_proc.process(batch);
              (*m_post)(batch);
          }
      
      private:
          Processor m_proc;
          Handle m_pre;
          Handle m_post;
          Foo* m_previous;
      }
      

      这样,您可以使用您想要的任何前/后处理逻辑创建任何自定义的 Foo 对象。如果创建是重复的,您总是可以将其提取到FooFactory 类的createXXX 方法中。

      P/S:如果你不喜欢函数指针,你可以使用任何代表函数的东西,例如带有一个方法的接口,或者 lambda 表达式...

      【讨论】:

        猜你喜欢
        • 2013-05-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-10-18
        • 1970-01-01
        • 1970-01-01
        • 2013-04-04
        • 2011-02-11
        相关资源
        最近更新 更多