4

I have a task to implement fluent interface for a class, which consist of other classes. Let's say we have a class:

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

I would like to use code like:

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

but I also would like to forbid code like:

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

I thought about a class inherited from FooBuilder which is returned after calling .settingP1() but I am not sure how to do it. Notice that I don't want to write .build() when I ended specifying Pizza object.

EDIT: Maybe I should've mentioned that when I wrote .settingP2().setPrice(2) without writing .setSize(sth) I meant that size will just have default value. I want to be able to "jump" to the next object regardless of specifying all attributes or not

EDIT2: I know how to implement the Builder pattern and fluent interface for classes which have fields of basic types. The problem is I want the code

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

to not compile. Maybe it's impossible to write such a builder.

wisniak
  • 163
  • 7
  • You can write setters which returns reference to objects, like `Foo& setName( const std::string &name )`, e.t.c. – malchemist Jan 18 '16 at 21:50
  • You can return the `FooBuilder` reference in your setter methods. – Jason Jan 18 '16 at 21:53
  • Why not just create a Pizza builder and put `name` into the `Pizza` class? It makes sense that way. – Poriferous Jan 18 '16 at 21:56
  • @Poriferous Yes, but I still can't see how it could prevent the second piece of code to not compile. I could write everything in `PIzza` but then I would have setter for every object in every pizza - I don't want this – wisniak Jan 18 '16 at 22:04
  • 1
    @wisniak That's nonsense. There's nothing wrong with having getters and setters that pertain to `Pizza` within the actual class itself. Think outside the scope of your architecture: if I were to use your code as-is I wouldn't even know what I should be doing. I'd assume the getters and setters are in `Pizza`; for it to not be is a major flaw in design imo. It's like asking for a takeout pizza only to find the operator is redirecting you to another company to do it for them. There's nothing wrong with having delegates, but if the original interface is disrupted then it's a folly. – Poriferous Jan 18 '16 at 23:23
  • Related questions: http://stackoverflow.com/questions/1638722/how-to-improve-the-builder-pattern and http://stackoverflow.com/questions/6613429/how-to-ensure-that-builder-pattern-is-completed – Fuhrmanator Jan 19 '16 at 14:35

4 Answers4

4

If you don't mind, I'll write solution for your problem in Java, hopefully you'll be able to apply it in C++ without anyu problem.

You have 2 options.

  1. More verbose DSL (I prefer not to call your problem Builder any more, but either Fluent API, or DSL - Domain Specific Language, as it defines grammar rules for it) with simpler implementation
  2. or simpler DSL (exactly what you wrote) with a small trick in the implenmentation.

For optiona #1 your usage would look like this:

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

Notice additional methods end(). Corresponding code in Java would look like this:

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;
    }
}

Now for option #2 I'd do another generalization to your problem to allow defining any number of pizzas. I'd also omit set prefix, it's not usual for DSL:

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

Now the implementation will look like:

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();
    }
}

There is one noticeable attribute - circular dependency. FooBuilder must know PizzaBuilder, and PizzaBuilder must know FooBuilder. In Java it's not an issue. If I remember correctly, you can solve it in C++ too by declaring first just the type using forward declaration or so.

It would also be typically beneficial for the second example in Java to introduce an interface with methods build() and addPizzaWith(), which both classes implement. So you can e.g. add pizzas in cycle without any issue.

Ondřej Fischer
  • 411
  • 1
  • 7
  • 16
3

Dmitri Nesteruk has written a "facet builder" example that is pretty much what you are trying to achieve.

The basic structure would be something like (almost pseudo code):

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; }

};
Antonio Barreto
  • 133
  • 2
  • 8
1

You can add a FooPizzaBuilder class as derrivate of FooBuilder.

By doing this you seperate the building of your Pizza classes and the building of the actual Foo class.

Consider the following code:

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()
}

This requires you to call settingP1() before making a call to setPrice();

0

An easy way to make the code type safe is to add an enum class to FooBuilder.

class FooBuilder {

  public:

    enum class PizzaNum {
      ONE,
      TWO
    }
}

and...

  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;
}

Then, you need to pass the enum to the method otherwise it results in a compile time error (e.g. .setPrice(FooBuilder::PizzaNum::ONE, 5).

Note, this is non-variadic.

Jason
  • 3,777
  • 14
  • 27