Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement nested fluent interface?

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.

like image 277
wisniak Avatar asked Mar 13 '23 14:03

wisniak


2 Answers

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

};
like image 40
Antonio Barreto Avatar answered Mar 23 '23 20:03

Antonio Barreto


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.

like image 192
Ondřej Fischer Avatar answered Mar 23 '23 22:03

Ondřej Fischer