构建器模式:确保对象完全构建

Jon*_*an. 13 c++ builder builder-pattern c++11

例如,如果我有一个构建器,那么我可以像这样创建对象:

Node node = NodeBuilder()
            .withName(someName)
            .withDescription(someDesc)
            .withData(someData)
            .build();
Run Code Online (Sandbox Code Playgroud)

如何确保在构建方法之前已经设置了用于构建对象的所有变量?

例如:

Node node = NodeBuilder()
            .withName(someName)
            .build();
Run Code Online (Sandbox Code Playgroud)

不是一个有用的节点,因为尚未设置描述和数据.

我使用构建器模式的原因是因为没有它,我需要很多构造函数的组合.例如,可以通过获取Field对象来设置名称和描述,并且可以使用文件名设置数据:

Node node = NodeBuilder()
            .withField(someField) //Sets name and description 
            .withData(someData) //or withFile(filename)
            .build(); //can be built as all variables are set
Run Code Online (Sandbox Code Playgroud)

否则将需要4个构造函数(字段,数据),(字段,文件名),(名称,描述,数据),(名称,描述,文件名).当需要更多参数时会变得更糟.

这些"方便"方法的原因是因为必须构建多个节点,因此它可以节省大量重复的行,如:

Node(modelField.name, modelField.description, Data(modelFile)),
Node(dateField.name, dateField.description, Data(dateFile)),
//etc
Run Code Online (Sandbox Code Playgroud)

但是在某些情况下,需要使用非来自文件的数据构建节点,和/或名称和描述不基于字段.也可能有多个节点共享相同的值,因此不是:

Node(modelField, modelFilename, AlignLeft),
Node(dateField, someData, AlignLeft),
//Node(..., AlignLeft) etc
Run Code Online (Sandbox Code Playgroud)

你可以有:

LeftNode = NodeBuilder().with(AlignLeft);

LeftNode.withField(modelField).withFile(modelFilename).build(),
LeftNode.withField(dateField).withData(someData).build()
Run Code Online (Sandbox Code Playgroud)

所以我认为我的需求与构建器模式非常匹配,除了构建不完整对象的能力.由于上述原因,"在构造函数中放置必需参数并具有可选参数的构建器方法"的正常建议不适用于此处.

实际问题:如何在编译时调用构建之前确保已设置所有参数?我正在使用C++ 11.

(在运行时,我可以为每个参数设置一个标志位,并声明所有标志都在构建中设置)

或者是否有一些其他模式来处理大量的构造函数组合?

Hor*_*ing 9

免责声明:这只是一个快速的镜头,但我希望它可以让你了解你需要什么.

如果您希望这是编译器时间错误,编译器需要在构造的每个阶段了解当前设置的参数.您可以通过为当前设置的参数的每个组合设置不同的类型来实现此目的.

template <unsigned CurrentSet>
class NodeBuilderTemplate
Run Code Online (Sandbox Code Playgroud)

这使得设置参数成为NodeBuilder类型的一部分; CurrentSet用作位域.现在,您需要为每个可用参数添加一些内容:

enum
{
    Description = (1 << 0),
    Name = (1 << 1),
    Value = (1 << 2)
};
Run Code Online (Sandbox Code Playgroud)

您从没有设置参数的NodeBuilder开始:

typedef NodeBuilderTemplate<0> NodeBuilder;
Run Code Online (Sandbox Code Playgroud)

并且每个setter必须返回一个新的NodeBuilder,并将相应的位添加到位域:

NodeBuilderTemplate<CurrentSet | BuildBits::Description> withDescription(std::string description)
{
    NodeBuilderTemplate nextBuilder = *this;
    nextBuilder.m_description = std::move(description);
    return nextBuilder;
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以static_assertbuild函数中使用a 来确保CurrentSet显示设置参数的有效组合:

Node build()
{
    static_assert(
        ((CurrentSet & (BuildBits::Description | BuildBits::Name)) == (BuildBits::Description | BuildBits::Name)) ||
        (CurrentSet & BuildBits::Value),
        "build is not allowed yet"
    );

    // build a node
}
Run Code Online (Sandbox Code Playgroud)

每当有人尝试拨打这将触发一个编译时错误build()NodeBuilder是缺少某些参数.

运行示例:http://coliru.stacked-crooked.com/a/8ea8eeb7c359afc5


Iva*_*rop 0

免责声明:这是一个想法。我不确定它是否有效。只是分享。

您可以尝试:

  • 从中删除build()方法NodeBuilder
  • 将您的必填字段重新组合为单个构建器方法NodeBuilder,例如NodeBuilder::withFieldData(bla, bli, blu)和/或NodeBuilder::withFieldData(structBliBlaBLU)
  • makewithFieldData()返回不同类型的构建器,例如NodeBuilderFinal. 只有这种类型的建造者才有build()方法。您可以从 继承非强制方法NodeBuilder。(严格来说,NodeBuilderFinal是一个“Proxy”对象)

这将强制用户调用withFieldData()before build(),同时允许以任意顺序调用其他构建器方法。任何调用build()非最终构建器的尝试都会触发编译器错误。build()在最终构建器完成之前,该方法不会显示在自动完成中;)。

如果您不需要整体withFieldData方法,您可以从每个“字段”方法返回不同的代理,例如NodeBuilderWithNameNodeBuilderWithFile,并且从这些方法中,您可以返回NodeBuilderWithNameAndFile等,直到构建最终构建器。这是非常棘手的,需要引入许多类来覆盖“现场”调用的不同顺序。与 @ClaasBontus 在评论中提出的建议类似,您可以使用模板来概括和简化它。

理论上,您可以尝试通过在链中引入更多代理对象来强制执行更复杂的约束。