我有一个任务是为一个类实现流畅的接口,该类由其他类组成。我们假设我们有一个班级:
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);
不编译。也许写这样的建筑师是不可能的。
答案 0 :(得分:2)
如果您不介意,我会为您的Java问题编写解决方案,希望您能够在C ++中应用它而不会遇到任何问题。
您有2个选择。
对于选项#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()