如何避免这种类似单身的设计模式?

时间:2014-10-31 19:14:35

标签: c++ design-patterns singleton

我们有一个渲染管道,我们当前的代码为管道的每个阶段创建一个实例。这意味着当我们更新代码时,我们将不断更新管道(或多组管道)代码。这感觉就像我们应该有额外的抽象,但我们不确定如何继续。

编辑:看来我的伪代码还不太清楚。也许图表将更容易解释模式。

链接到方框图:http://yuml.me/0650d1bf.svg

// yuml.me
[GenericRenderStage|render|Parent Class]<---[Shadow1RenderStage|render|Derived Class]
[Shadow1RenderStage|render|Derived Class]<---[_shadowRenderStage1|Singleton Object]
[GenericRenderStage|render|Parent Class]<---[Shadow2RenderStage|render|Derived Class]
[Shadow2RenderStage|render|Derived Class]<---[_shadowRenderStage2|Singleton Object]
[GenericRenderStage|render|Parent Class]<---[ShadowNRenderStage|render|Derived Class]
[ShadowNRenderStage|render|Derived Class]<---[_shadowRenderStageN|Singleton Object]

父类的Psuedo-c ++代码:

class GenericRenderStage(...) {
    /// Render method
    virtual void render(void) {
        /// handles drawing code
    }

class Shadow1RenderStage : GenericRenderStage(...) {
    /// Render method
    void render(void) {
        /// handles custom drawing for shadow1 stage
    }

class Shadow2RenderStage : GenericRenderStage(...) {
    /// Render method
    void render(void) {
        /// handles custom drawing for shadow2 stage
    }

...

class ShadowNRenderStage : GenericRenderStage(...) {
    /// Render method
    void render(void) {
        /// handles custom drawing for shadowN stage
    }

然后我们为管道提供了一组相同类型的模式......

class GenericRenderPipeLine(...) {
    /// Render method
    virtual void render(void) {
        /// handles drawing code
    }

class ShadowRenderPipeline : GenericRenderPipeLine() { 
    /// instantiate stages for this pipeline
    ShadowRenderPipeline() {
        shadow1Stage = new Shadow1RenderStage();
        shadow2Stage = new Shadow2RenderStage();
        ...
        shadowNStage = new ShadowNRenderStage();
    } 

    /// Render method
    void render(void) { 
        /// setup fbo
        /// for each render stage, render
        shadow1Stage.render()
        shadow2Stage.render()
        ...
        shadowNStage.render()
        /// handle fbo
}

这里的某些东西看起来真的错了。我们有一个父类,它基本上是一组虚拟方法,可以由只有一个实例的自定义类继承。

2 个答案:

答案 0 :(得分:2)

根据我的理解(如果我错了请纠正我),每个渲染阶段都是独一无二的,并不真正遵循任何特定的模式。所以,我认为最好保持他们的方式;将每个渲染阶段放在一个自包含的文件/类中。

但是,我认为通过消除渲染管道继承结构可以减少大量工作。所有这些似乎是相同的(即它们具有一些阶段并且在它们中的每一个上调用render())。如果你有一个动态的通用管道怎么办?

#include <vector>
#include <memory>

class DynamicRenderPipeline {

private:
    std::vector<std::unique_ptr<GenericRenderStage>> renderStages;

public:

    void add(std::unique_ptr<GenericRenderStage> renderStage) {
        renderStages.push_back(std::move(renderStage));
    }

    void render() {
        for (auto& stage : renderStages) {
            stage->render();
        }
    }
};

int main() {
    DynamicRenderPipeline pipeline;
    pipeline.add(std::unique_ptr<GenericRenderStage>(new RenderStage1()));
    pipeline.add(std::unique_ptr<GenericRenderStage>(new RenderStage2()));
    pipeline.add(std::unique_ptr<GenericRenderStage>(new RenderStage3()));
    pipeline.add(std::unique_ptr<GenericRenderStage>(new RenderStage4()));
    pipeline.add(std::unique_ptr<GenericRenderStage>(new RenderStage5()));

    pipeline.render();
}

您现在可以只创建DynamicRenderPipeline的实例并添加您想要的任何阶段。当你在它上面调用render()时,它将以正确的顺序循环遍历所有添加的渲染阶段。现在您的管道只依赖于GenericRenderStage接口。如果您不使用C ++ 11,您可以使用原始指针(而不是unique_ptr)执行相同的操作,但是您必须确保在管道的析构函数中清理向量。

答案 1 :(得分:0)

事实上,您只实例化了您创建的许多类中的每一个类的一个对象,这是基于类的OOPàlaJava / C ++中常见问题的表现。在C ++中比在Java中更容易规避。

问题很简单,如果你想要基于纯类的OO方式,所有代码都必须驻留在类定义中。例如,在Java中,如果要创建许多截然不同的比较器(两个相同类型的值的布尔谓词),理想情况下,您必须创建与标准Java通用接口比较器一样多的子类。实际上,我撒了谎,因为在很多情况下你可以使用

new Comparator<Type>() {
    public bool compare(Type a, Type b) { /* */ }
}

语法,这是Java 8之前拥有一流公民功能的最接近的Java。但是,当您想要允许创建自定义比较器时,即使该语法也不起作用,然后您必须编写像这样的整个班级,以便无效重复自己:

class CompareIntsPlusX implements Comparator<int>
{
    private int number;

    public CompareIntsPlusX(int number) {
        this.number = number;
    }

    public bool compare(int x, int y) {
        return x < y + number;
    }
}

Comparator<int> myComparator = new CompareIntsPlusX(3);

我们最终会使用非常繁琐的语法来处理最简单的对象。请注意,在任何(某种程度上)函数编程语言(如Javascript)中,此问题将更容易解决。

这里也发生了同样的现象。您已经在管道阶段定义中使用基于类的OO方式,现在您意识到它不可伸缩,因为您只需要一个带有它的类的对象,它需要很多这样的对象。

即使他们不以任何方式遵循Singleton设计模式,这些对象也被称为“单身人士”。他们是事实上的单身人士,其原因在于他们实际上只是伪装的功能。他们唯一的目标是随身携带void render()方法。

从这里你有几个解决方案:

  • 如果render函数差异很大,并且你不能从一个接受参数的类生成它们,就像我们上面用CompareIntsPlusX Java类所做的那样,也就是说你不能考虑你的代码,然后

    • 如果您使用的是C ++ 03(我不推荐)或C ++ 11的功能样式(我从未使用过我要建议的内容),那么您可以简单地使用一个RenderStage类定义如下:

      class RenderStage
      {
      private:
          std::function<void()> renderFunction;
      
      public:
          RenderStage(std::function<void()> renderFunction)
              : renderFunction(renderFunction)
          { }
      
          void render() {
              renderFunction();
          }
      }
      
      /* Somewhere else in the code, create your render stages */
      RenderStage r1([]() {
          // blah blah
      });
      // You can also capture stuff!
      Texture tex;
      RenderStage r2([&tex]() {
          // blah blah
      });
      

      在C ++ 03中,我们将使用函数指针(其函数在命名空间中定义,因为我们不想污染全局命名空间)。请注意,在C ++ 11中,如果lambdas中需要捕获,我们只需要使用std::function。如果没有,那么只需使用函数指针。

    • 否则,如果您想继续使用OO样式,那么可以根据需要创建任意数量的类,并且不要感到不好。实际上,创建匿名类是编译器在创建lambdas时背后所做的事情(事实上,如果你的lambdas有捕获而创建类,如果没有,则创建简单函数),Java编译器在你“实例化接口”时也会这样做。

  • 否则,如果可以考虑render个方法,即如果不是N个类,你可以使用一个允许自定义的构造函数,那么就这样做。

第一种和第二种解决方案的混合可能是您所需要的。在进入更复杂的解决方案之前,您应该尝试使用可自定义渲染阶段对象的第二种解决方案。