引言——构造复杂对象的艺术
软件工程中,构造复杂对象的艺术被巧妙地封装在构造者模式(Builder Pattern)中。这种设计模式不仅提供了一种清晰且灵活的方式来构建复杂对象,还使得代码更具可读性和可维护性。构造者模式的核心思想是将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
构造者模式下的蟹堡王
在《海绵宝宝》动画片中,蟹堡王可谓是比基尼海滩最著名的快餐店,其中就属蟹黄堡最出名(常常受到痞老板觊觎)。蟹堡王还是海绵宝宝、章鱼哥工作的地方,老板则是蟹老板。
我们可以取构造者模式为视角,看下蟹黄堡的制作过程中,各个角色的构成成分。
产品(Product):毋庸置疑,肯定是蟹黄堡
蟹黄堡由多个部分组成,包括面包、肉饼、生菜、番茄、奶酪、洋葱、泡菜和特制酱料。
构造者(Builder):蟹黄堡的制作步骤
定义制作蟹黄堡的各个步骤,如添加面包、添加肉饼、添加生菜等。
具体构造者(Concrete Builder):蟹堡王唯一的厨师——海绵宝宝
在动画片里,由海绵宝宝负责按照特定的顺序和配方制作蟹黄堡。
指挥者(Director):蟹堡王的黑心老板——蟹老板
蟹老板负责指挥海绵宝宝制作美味蟹黄堡。
蟹黄堡的制作流程
可以看到构造者模式应用下的在蟹堡王,蟹黄堡的制作过程会被分解为多个步骤,每个步骤都由海绵宝宝来执行,而蟹老板则负责指挥整个制作过程。这种模式使得蟹黄堡的制作过程既灵活又可控。
1. 首先是定义产品类——蟹黄堡
import lombok.Data;
@Data
public class KrabbyPatty {
private String bread;
private String patty;
private String lettuce;
private String tomato;
private String cheese;
private String onion;
private String pickle;
private String sauce;
private String love;
@Override
public String toString() {
return "KrabbyPatty [bread=" + bread + ", patty=" + patty + ", lettuce=" + lettuce + ", tomato=" + tomato + ", cheese=" + cheese + ", onion=" + onion + ", pickle=" + pickle + ", sauce=" + sauce + ", love=" + love + "]";
}
}
2. 然后是定义构造者接口——蟹黄堡的制作步骤
public interface KrabbyPattyBuilder {
void buildBread();
void buildPatty();
void buildLettuce();
void buildTomato();
void buildCheese();
void buildOnion();
void buildPickle();
void buildSauce();
void buildLove();
KrabbyPatty getKrabbyPatty();
}
3. 接着是创建一个具体构造者——海绵宝宝
public class Spongebob implements KrabbyPattyBuilder {
private KrabbyPatty krabbyPatty;
public Spongebob() {
this.krabbyPatty = new KrabbyPatty();
}
@Override
public void buildBread() {
krabbyPatty.setBread("面包");
}
@Override
public void buildPatty() {
krabbyPatty.setPatty("汉堡肉");
}
@Override
public void buildLettuce() {
krabbyPatty.setLettuce("生菜");
}
@Override
public void buildTomato() {
krabbyPatty.setTomato("西红柿");
}
@Override
public void buildCheese() {
krabbyPatty.setCheese("芝士片");
}
@Override
public void buildOnion() {
krabbyPatty.setOnion("洋葱片");
}
@Override
public void buildPickle() {
krabbyPatty.setPickle("酸黄瓜");
}
@Override
public void buildSauce() {
krabbyPatty.setSauce("秘制酱汁");
}
public void buildLove() {
krabbyPatty.setLove("满满的爱");
}
@Override
public KrabbyPatty getKrabbyPatty() {
return krabbyPatty;
}
}
4. AndThen!指挥者——蟹老板
public class Krabs {
private KrabbyPattyBuilder builder;
public Krabs(KrabbyPattyBuilder builder) {
this.builder = builder;
}
public void constructKrabbyPatty() {
builder.buildBread();
builder.buildPatty();
builder.buildLettuce();
builder.buildTomato();
builder.buildCheese();
builder.buildOnion();
builder.buildPickle();
builder.buildSauce();
builder.buildLove();
}
public KrabbyPatty getKrabbyPatty() {
return builder.getKrabbyPatty();
}
}
5. 最后,当然是开始享用美味蟹黄堡啦
public class Client {
public static void main(String[] args) {
KrabbyPattyBuilder builder = new Spongebob();
Krabs director = new Krabs(builder);
director.constructKrabbyPatty();
KrabbyPatty krabbyPatty = director.getKrabbyPatty();
System.out.println(krabbyPatty);
}
}
上面的例子是一个标准的构造者模式。在这个模式中,将蟹黄堡的制作过程与产品互相分离。蟹老板(Krabs
)作为指挥者,不需要关心海绵宝宝(具体构造者)是如何构建蟹黄堡的,只需要调用相应的构建方法坐收小钱钱。这正是构造者模式的特点之一:将构建复杂对象的过程分解为多个步骤,并且这些步骤可以灵活组合,从而构建出不同的产品。
标准构造者模式的变种
上面的构建者模式是一个标准的写法,在实际的开发中见到的和应用的构造者模式大多都不会是完全按照这种四个角色来编写,更多的采用了构造者模式的演变版本,以适应不同的需求和场景。例如StringBuilder、Retrofit、OkHttp 等。下面我们来列举几种常用的变种模式写法:
内部构造者(Inner Builder)
这种写法,核心是在产品类内部定义一个静态内部类作为构造者,指挥者(Director)角色通常被省略,因为客户端可以直接使用内部构造者来构建产品。这样可以使得代码更加紧凑和简洁。也是目前最常用的一种构造者模式写法。
@Data
public class KrabbyPatty {
private String bread;
private String patty;
private String lettuce;
private String tomato;
private String cheese;
private String onion;
private String pickle;
private String sauce;
private String love;
// 私有构造函数,只能通过内部构造者创建
private KrabbyPatty(Spongebob builder) {
this.bread = builder.bread;
this.patty = builder.patty;
this.lettuce = builder.lettuce;
this.tomato = builder.tomato;
this.cheese = builder.cheese;
this.onion = builder.onion;
this.pickle = builder.pickle;
this.sauce = builder.sauce;
this.love = builder.love;
}
public static Spongebob newBuilder() {
return new Spongebob();
}
public static class Spongebob {
private String bread;
private String patty;
private String lettuce;
private String tomato;
private String cheese;
private String onion;
private String pickle;
private String sauce;
private String love;
public Spongebob buildBread(String bread) {
this.bread = bread;
return this;
}
public Spongebob buildPatty(String patty) {
this.patty = patty;
return this;
}
public Spongebob buildLettuce(String lettuce) {
this.lettuce = lettuce;
return this;
}
public Spongebob buildTomato(String tomato) {
this.tomato = tomato;
return this;
}
public Spongebob buildCheese(String cheese) {
this.cheese = cheese;
return this;
}
public Spongebob buildOnion(String onion) {
this.onion = onion;
return this;
}
public Spongebob buildPickle(String pickle) {
this.pickle = pickle;
return this;
}
public Spongebob buildSauce(String sauce) {
this.sauce = sauce;
return this;
}
public Spongebob buildLove(String love) {
this.love = love;
return this;
}
public KrabbyPatty build() {
return new KrabbyPatty(this);
}
}
@Override
public String toString() {
return "KrabbyPatty [bread=" + bread + ", patty=" + patty + ", lettuce=" + lettuce + ", tomato=" + tomato + ", cheese=" + cheese + ", onion=" + onion + ", pickle=" + pickle + ", sauce=" + sauce + ", love=" + love + "]";
}
接着是客户端的调用,我们可以明显的发现,与标准的构造者模式相比,内部构造者看起来更加简洁清晰。
public class Clinet {
public static void main(String[] args) {
KrabbyPatty krabbyPatty = KrabbyPatty.newBuilder()
.buildBread("面包")
.buildPatty("肉")
.buildLettuce("莴苣")
.buildTomato("西红柿")
.buildCheese("奶酪")
.buildOnion("黄瓜")
.buildPickle("蟹黄")
.buildSauce("番茄酱")
.buildLove("满怀诚意的爱")
.build();
System.out.println(krabbyPatty);
}
}
链式构造者(Fluent Builder)
这种写法,将每个构建方法返回构造者自身,以支持链式调用。指挥者(Director)角色通常被省略,因为客户端可以直接使用链式构造者来构建产品。这使得代码更加简洁和易读。
public class KrabbyPatty {
private String bread;
private String patty;
private String lettuce;
private String tomato;
private String cheese;
private String onion;
private String pickle;
private String sauce;
private String love;
// 私有构造函数,只能通过内部构造者创建
private KrabbyPatty() {}
public static Spongebob newBuilder() {
return new Spongebob();
}
public static class Spongebob {
private KrabbyPatty krabbyPatty = new KrabbyPatty();
public Spongebob buildBread(String bread) {
krabbyPatty.bread = bread;
return this;
}
public Spongebob buildPatty(String patty) {
krabbyPatty.patty = patty;
return this;
}
public Spongebob buildLettuce(String lettuce) {
krabbyPatty.lettuce = lettuce;
return this;
}
public Spongebob buildTomato(String tomato) {
krabbyPatty.tomato = tomato;
return this;
}
public Spongebob buildCheese(String cheese) {
krabbyPatty.cheese = cheese;
return this;
}
public Spongebob buildOnion(String onion) {
krabbyPatty.onion = onion;
return this;
}
public Spongebob buildPickle(String pickle) {
krabbyPatty.pickle = pickle;
return this;
}
public Spongebob buildSauce(String sauce) {
krabbyPatty.sauce = sauce;
return this;
}
public Spongebob buildLove(String love) {
krabbyPatty.love = love;
return this;
}
public KrabbyPatty build() {
return krabbyPatty;
}
}
@Override
public String toString() {
return "KrabbyPatty [bread=" + bread + ", patty=" + patty + ", lettuce=" + lettuce + ", tomato=" + tomato + ", cheese=" + cheese + ", onion=" + onion + ", pickle=" + pickle + ", sauce=" + sauce + ", love=" + love + "]";
}
}
客户端的写法与内部构造者一致,这里就不多赘述了。
与抽象工厂结合使用
这种写法是将构造者模式与抽象工厂模式结合,抽象工厂模式负责创建对象的各个组成部分,而构造者模式负责将这些部分组装成一个复杂的对象。这种分离使得代码更加清晰和易于维护。且使得代码变得更加灵活,后续如果蟹堡王需要增加别的种类的汉堡包,无需修改到现有的蟹黄堡配方,只需要在现有的基础上新增或更改材料即可。
1. 首先定义一个抽象类,由于创建汉堡的各个组成成分。
public abstract class KrabbyPattyFactory {
public abstract String createPatty();
public abstract String createCheese();
public abstract String createSauce();
}
2. 创建具体抽象实现类,返回特定类型的汉堡组成部分
public class RegularPattyFactory extends KrabbyPattyFactory {
@Override
public String createPatty() {
return "汉堡肉饼";
}
@Override
public String createCheese() {
return "芝士片";
}
@Override
public String createSauce() {
return "番茄酱";
}
}
3. 创建主要类,用于让海绵宝宝将这些材料组合成蟹黄堡。
public class KrabbyPatty {
private String patty;
private String cheese;
private String sauce;
// 私有构造函数,只能通过内部构造者创建
private KrabbyPatty(Spongebob builder) {
this.patty = builder.patty;
this.cheese = builder.cheese;
this.sauce = builder.sauce;
}
public static Spongebob newBuilder() {
return new Spongebob();
}
public static class Spongebob {
private String patty;
private String cheese;
private String sauce;
public Spongebob withFactory(KrabbyPattyFactory factory) {
this.patty = factory.createPatty();
this.cheese = factory.createCheese();
this.sauce = factory.createSauce();
return this;
}
public KrabbyPatty build() {
return new KrabbyPatty(this);
}
}
@Override
public String toString() {
return "KrabbyPatty [patty=" + patty + ", cheese=" + cheese + ", sauce=" + sauce + "]";
}
}
4. 使用客户端调用
public class Client {
public static void main(String[] args) {
KrabbyPattyFactory factory = new RegularPattyFactory();
KrabbyPatty krabbyPatty = KrabbyPatty.newBuilder()
.withFactory(factory)
.build();
System.out.println(krabbyPatty);
}
}
总结
构造者模式允许你一步步地构建一个复杂的对象,而不是一次性地创建它。这样做的好处是,你可以灵活地调整对象的各个部分,而不需要为每一种可能的组合都创建一个新的类。就像是在蟹堡王定制蟹黄堡一样,你可以根据自己的需求,让小海绵一步步地构建出你想要的汉堡。这样既灵活又有趣,不是吗?