如何实现嵌套的流畅界面?

时间:2016-01-18 21:21:46

标签: c++ oop design-patterns

我有一个任务是为一个类实现流畅的接口,该类由其他类组成。我们假设我们有一个班级:

class Pizza {
    int price, size;
}
class Foo {
    string name;
    Pizza p1, p2;
}

我想使用如下代码:

Foo f = FooBuilder().setName("foo")
        .settingP1().setPrice(5).setSize(1)
        .settingP2().setPrice(2)
        .build();

但我也想禁止代码:

Foo f = FooBuilder().setName("foo").setPrice(5);

我想过一个从FooBuilder继承的类,它在调用.settingP1()之后返回,但我不知道该怎么做。请注意,当我结束指定Pizza对象时,我不想写.build()

编辑:也许我应该提到当我写.settingP2().setPrice(2)而不写.setSize(sth)时,我的意思是这个大小只有默认值。我希望能够跳过#34;无论是否指定所有属性

,都是下一个对象

EDIT2:我知道如何为具有基本类型字段的类实现Builder模式和流畅接口。问题是我想要代码

Foo f = FooBuilder().setName("foo").setPrice(5);

不编译。也许写这样的建筑师是不可能的。

4 个答案:

答案 0 :(得分:2)

如果您不介意,我会为您的Java问题编写解决方案,希望您能够在C ++中应用它而不会遇到任何问题。

您有2个选择。

  1. 更详细的DSL(我不愿再称呼您的问题生成器,而是Fluent API或DSL-特定于域的语言,因为它为其定义了语法规则)
  2. 或更简单的DSL(正是您编写的内容),但在实现方面却有一个小技巧。

对于选项#1,您的用法如下:

new FooBuilder().setName("Foo")
 .settingP1().setPrice(5).setSize(1).end()
 .settingP2().setPrice(2).end()
 .build();

请注意其他方法end()。 Java中的相应代码如下所示:

public class FooBuilder {
    public FooBuilder setName(String name) {
        // Store the name
        return this;
    }
    public PizzaBuilder settingP1() {
        return new PizzaBuilder(pizza1, this);
    }
    public PizzaBuilder settingP2() {
        return new PizzaBuilder(pizza2, this);
    }
    public Foo build() {
        // return Foo build using stored information
    }
}

public class PizzaBuilder {
    private final Pizza pizza;
    private final FooBuilder foo;
    // Constructor
    public PizzaBuilder(Pizza pizza, FooBuilder foo) {
        this.pizza = pizza;
        this.foo = foo;
    }
    public PizzaBuilder setPrice(int price) {
        // update pizza price
        return this;
    }
    public PizzaBuilder setSize(int size) {
        // update pizza size
        return this;
    }
    // With this method you return to parent, and you can set second pizza.
    public FooBuilder end() {
        return foo;
    }
}

现在,对于选项2,我将对您的问题进行另一种概括,以允许定义任意数量的披萨。我还要省略set前缀,这对于DSL来说并不常见:

new FooBuilder().name("Foo")
    .addPizzaWith().price(5).size(1)
    .addPizzaWith().price(2)
    .build();

现在实现如下:

public class FooBuilder {
    public FooBuilder(String name) {
        // Store name
        return this;
    }
    public PizzaBuilder addPizzaWith() {
        Pizza pizza = createAndStorePizza(); // Some private method to do what is says
        return new PizzaBuilder(pizza, this);
    }
    public Foo build() {
        // Build and return the Foo using stored data
    }
}

public class PizzaBuilder {
    private final Pizza pizza;
    private final FooBuilder foo;
    public PizzaBuilder(Pizza pizza, FooBuilder foo) {
        this.pizza = pizza;
        this.foo = foo;
    }
    public PizzaBuilder price(int value) {
        // Store price value
        return this;
    }
    public PizzaBuilder size(int value) {
        // Store size value
        return this;
    }
    // This method does the trick - it terminates first pizza specification,
    // and delegates entering second (or any other) pizza specification to
    // the parent FooBuilder.
    public PizzaBuilder addPizzaWith() {
        return foo.addPizzaWith();
    }
    // Another similar trick with allowing to call build directly on Pizza
    // specification
    public Foo build() {
        return foo.build();
    }
}

有一个值得注意的属性-循环依赖。 FooBuilder必须知道PizzaBuilder,而PizzaBuilder必须知道FooBuilder。在Java中,这不是问题。 如果我没记错的话,也可以通过先声明仅 使用前向声明进行输入。

对于Java中的第二个示例来说,引入两个方法都实现的方法build()addPizzaWith()的接口通常也将是有益的。所以你可以循环添加比萨饼没有任何问题。

答案 1 :(得分:1)

Dmitri Nesteruk has written a "facet builder"这个例子几乎就是你想要实现的目标。

基本结构类似于(几乎是伪代码):

class FooBuilderBase {
    protected:
        Foo& foo; // reference to derived builders
        FooBuilderBase(Foo& f) : foo(f) {}
    public:
        PizzaBuilder settingP1() { return PizzaBuilder(foo, foo.p1); }
        PizzaBuilder settingP2() { return PizzaBuilder(foo, foo.p2); }
};

class FooBuilder : public FooBuilderBase {
        Foo foo_; // real instance
    public:
        FooBuilder() : FooBuilderBase(foo_) {}
        FooBuilder& setName(string n) { foo.name = n; return *this; }
};

class PizzaBuilder : public FooBuilderBase {
        Pizza& pizza;
    public:
        PizzaBuilder(Foo& f, Pizza& p) : FooBuilderBase(f), pizza(p) {}
        PizzaBuilder& setPrice(int p) { pizza.price = p; return *this; }

};

答案 2 :(得分:0)

使代码类型安全的简便方法是向enum class添加FooBuilder

class FooBuilder {

  public:

    enum class PizzaNum {
      ONE,
      TWO
    }
}

和...

  FooBuilder& FooBuilder::setPrice(const PizzaNum pizzaNum, const int price) {

    switch (pizzaNum) {

      case PizzaNum::ONE:
        p1.setPrice(price);
        break;

      case PizzaNum::TWO:
        p2.setPrice(price);
        break;
    }

  return this;
}

然后,您需要将enum传递给方法,否则会导致编译时错误(例如.setPrice(FooBuilder::PizzaNum::ONE, 5)。

注意,这是非变量的。

答案 3 :(得分:0)

您可以添加FooPizzaBuilder类作为FooBuilder的原型。

通过这样做,您可以单独构建Pizza类并构建实际的Foo类。

请考虑以下代码:

enum class PizzaNum {
ONE, TWO
}

class FooPizzaBuilder;

class FooBuilder {
public:
    FooBuilder();
    FooBuilder setName();
    FooPizzaBuilder settingP1();
    FooPizzaBuilder settingP2();
    Foo build();

protected:
    void _setPrize(PizzaNum); //Don't expose _setPrice() to user
    void _setSize(PizzaNum);  //Don't expose _setSize() to user
}

class FooPizzaBuilder : public FooBuilder {
public:
    FooPizzaBuilder(PizzaNum pizzaNum)
    FooPizzaBuilder setPrice(); //Call _setPrice()
    FooPizzaBuilder setSize();  //Call _setSize()
}

这要求您在致电settingP1();

之前致电setPrice()