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.
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; }
};
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With