每个人都必须使用具有面向对象范例的语言来进行对象构造。 但是,当您的对象有很多成员并且随时可能更改时,如何创建一个健壮的API保护您的使用者免受非被动更改的影响? 如何避免使用多个构造函数,这些构造函数允许您的用户以不同的方式构造对象(我称之为构造函数地狱)?
An Example
假设您正在打开自己的披萨链,并且想要编写一个Java应用程序,该应用程序允许用户创建自己的披萨。
您要做的最合乎逻辑的事情是创建一个比萨允许您封装比萨饼概念的类:
public class Pizza {
private Collection<String> toppings;
private String sauce;
private boolean hasExtraCheese;
}
看起来还好吧? 我们可以为这些成员创建getter和setter方法,以允许我们更改对象的状态:
//... other code
public Collection<String> getToppings {
return this.toppings;
}
public void setToppings(Collection<String> toppings) {
this.toppings = toppings;
}
// ... other setters and getters
还不错...但是如何构造这些东西之一? 好吧,最简单的答案就是手动设置所有内容:
final Pizza pizza = new Pizza();
pizza.hasExtraCheese(true);
pizza.setSauce("garlic");
List<String> toppings = new ArrayList<String>();
toppings.add("pepperoni");
pizza.setToppings(toppings);
这还不错...但是很多代码...我们可以创建一个构造函数:
public Pizza(Collection<String> toppings, String sauce, boolean hasExtraCheese) {
// and then you set stuff...
}
这使得代码看起来像这样:
List<String> toppings = new ArrayList<String>();
toppings.add("pepperoni");
final Pizza pizza = new Pizza(toppings, "marinara", false);
哪一个还算不错...但是如果我不在乎指定是否需要额外的奶酪怎么办? 也许提供一种用默认酱汁构造披萨的方法会很方便。 在这一点上,您可能会想执行以下操作:
public Pizza(Collection<String> toppings) {
// default the sauce and extra cheese
}
public Pizza(String sauce, boolean hasExtraCheese) {
// default toppings as empty
}
//... potentially many more constructors
You could make so many different constructors as a convenience (anybody who writes Swift get that one?).
想知道是什么让这个构造函数变得如此糟糕?当人们开始想要自定义他们的披萨外壳时。
public class Pizza {
private Collection<String> toppings;
private String sauce;
private boolean hasExtraCheese;
private String crust; // OH NO, NEW THING I DIDN'T PLAN FOR!!! WE'RE DOOMED!
}
谁想写一打构造函数来支持初始化比萨 with an optional crust? Who wants to go create exponentially more after marketing tells you people want to customize their pizza with sauce drizzles and crust dust as a means to compete with 比萨 Hut?
...
没有人? 太酷了,让我们写一个Builder。
Builder Pattern
Builder模式允许您建立对象而不是构造 them. You provide an API in your 建立er that allows you to set all of the properties of a 比萨, and then the 建立er will 建立 the object for you:
public class PizzaBuilder() {
private Collection<String> toppings;
private String sauce;
private boolean hasExtraCheese;
private String crust;
public PizzaBuilder withToppings(Collection<String> toppings) {
this.toppings = toppings;
return this;
}
// ... create a "with" method for each member you want to set
public Pizza build() {
final Pizza pizza = new Pizza();
// set the pizza properties
return pizza;
}
}
这使你比萨创建容易得多,它结束了看起来更干净,它可以帮助您比萨s一成不变的, and your code isnow much more 被动于变化:
final Pizza pizza = new PizzaBuilder()
.withHasExtraCheese(true)
.withSauce("marinara")
.withCrust("pan")
.withToppings(new ArrayList<String>())
.build();
现在,当人们消耗您的比萨-API,如果您添加更多功能,则无需创建更多构造函数,而其他人则不必担心实现新功能。
How Dart Addresses This
Dart具有一些出色的语法,使我们可以跳过构建器的创建,并阻止我们进入构造器地狱。 让我们看一看比萨Dart课程:
class Pizza {
List<String> toppings;
String sauce;
bool hasExtraCheese;
}
One cool thing about Dart is that instance variables implement implicit getters and setters. If the instances are final, setters don't get generated.
我们完成了! 我们的消费者可以创造比萨实例,并且已经防范非被动更改!
...
不,我很认真。 您的工作完成了。 你做了必要的事。 你可以回家
Dart has an excellent feature called cascade notation that allows you to invoke getters, setters, and methods on object instances to instantiate them:
// don't mind me, just constructing a pizza...
var pizza = new Pizza()
..toppings = ['pepperoni', 'mushrooms']
..sauce = 'spaghetti'
..hasExtraCheese = true;
看起来很像建筑商,但实际上并非如此。 现在,如果我们添加更多实例变量,则上面的代码仍然可以正常工作。 我们的消费者以后可以根据需要添加外壳,无需重新组装。
希望您喜欢构建器模式,并希望它激发您对Dart的兴趣!