常见设计模式二:构造者模式
前言
构造者模式也是属于我们常用的设计的一种,比如我们在使用使用 StringBuilder、Retrofit、OkHttp 等,都使用了构造者模式,所以我认为构造者模式是工程师必须掌握的,不仅有助于我们看懂别人的代码,也有助于我们编写出高质量的代码。
什么是构造者模式
构造者模式又叫生成器模式, 构造者模式是将一个复杂对象构建和它的表示相分离,使得同样的构造过程可以创建出不同的对象。
也就是说我们只需要定义我们想要的对象的属性,最终就可以得到我们想要的对象,而不用去关心内部是怎么构建对象的。
常规的构造者模式有下面四个角色组成:
- Builder 构造对象的抽象,为创建一个产品 Product 的各个部件指定抽象
- ConcreteBuilder Builder 抽象的具体实现实现,负责实现各个部件的具体构造和装配方法
- Product 被构建的具体产品对象,其中包含多个部件
- Director 指挥者或者叫做导演类,负责安排复杂对象的构造次序,指挥者与抽象构造者之间存在关联关系
概念先了解到这里,下面通过一个例子去实现下构造者模式
标准的构造者模式
场景:
假如你来到了咖啡店,告诉店员要一杯拿铁咖啡,而拿铁咖啡的有下面几种构成:
- 咖啡浓缩 Espresso
- 热牛奶
- 奶泡
这个时候定义出咖啡类(对应上面讲的角色 3 Product):
/**
* 具体 Product 咖啡类
*/
public class Coffee {
/**
* 咖啡名字
*/
private String name;
/**
* 浓缩咖啡量
*/
private int espresso;
/**
* 奶泡量
*/
private int milkFoam;
/**
* 热牛奶量
*/
private int hotMilk;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getEspresso() {
return espresso;
}
public void setEspresso(int espresso) {
this.espresso = espresso;
}
public int getMilkFoam() {
return milkFoam;
}
public void setMilkFoam(int milkFoam) {
this.milkFoam = milkFoam;
}
public int getHotMilk() {
return hotMilk;
}
public void setHotMilk(int hotMilk) {
this.hotMilk = hotMilk;
}
@Override
public String toString() {
return "Coffee{" +
"name='" + name + '\'' +
", espresso=" + espresso +
", milkFoam=" + milkFoam +
", hotMilk=" + hotMilk +
'}';
}
}
制作咖啡需要分为以下几步:
- 首先要知道咖啡名字
- 制作浓缩 Espresso
- 制作热牛奶
- 制作奶泡
知道了制作流程,我们就可以定义构建抽象Builder(对应上面讲的角色 1 Builder)来描述咖啡的制作过程
Builder.java
/**
* 构建抽象类
*/
public abstract class Builder {
/**
* 制作咖啡的名字
*/
public abstract void buildName(String name);
/**
* 制作浓缩 Espresso
*/
public abstract void buildEspresso(int espresso);
/**
* 制作热牛奶
*/
public abstract void buildHotMilk(int hotMilk);
/**
* 制作奶泡
*/
public abstract void buildMilkFoam(int milkFoam);
/**
* 返回构建好的 Coffee
*
* @return 做好的 Coffee
*/
public abstract Coffee build();
}
有了具体的抽象类,我们得有具体的类去构建实际需要的咖啡,CoffeeBuilder.java(对应上面讲的角色 2 ConcreteBuilder) 继承 Builder 抽象类,完成咖啡的具体制作过程:
CoffeeBuilder.java
/**
* 具体咖啡构造过程
*/
public class CoffeeBuilder extends Builder {
private Coffee mCoffee;
public CoffeeBuilder() {
mCoffee = new Coffee();
}
@Override
public void buildName(String name) { mCoffee.setName(name); }
@Override
public void buildEspresso(int espresso) {
mCoffee.setEspresso(espresso);
}
@Override
public void buildHotMilk(int hotMilk) {
mCoffee.setHotMilk(hotMilk);
}
@Override
public void buildMilkFoam(int milkFoam) {
mCoffee.setMilkFoam(milkFoam);
}
/**
* 返回构造好的咖啡
*/
@Override
public Coffee build() {
return mCoffee;
}
}
这样就有了咖啡的制作过程,但是我们去买咖啡肯定是先告诉咖啡店的服务员,我们要叫什么咖啡,只需要和服务员交互就可以喝到想要的咖啡,于是构建一个咖啡店服务员类 CoffeeShopWaiter(对应于上面的角色 4 Director)来使用咖啡制作过程为我们提供咖啡。
CoffeeShopWaiter.java:
/**
* 咖啡店服务员
*/
public class CoffeeShopWaiter {
private CoffeeBuilder mCoffeeBuilder;
public void setCoffeeBuilder(CoffeeBuilder coffeeBuilder) {
mCoffeeBuilder = coffeeBuilder;
}
/**
* 制作咖啡
* @param name 名字
* @param espresso 浓缩量
* @param milkFoam 奶泡量
* @param hotMilk 牛奶量
* @return
*/
public Coffee makeCoffee(String name, int espresso, int milkFoam, int hotMilk) {
mCoffeeBuilder.buildName(name);
mCoffeeBuilder.buildEspresso(espresso);
mCoffeeBuilder.buildHotMilk(hotMilk);
mCoffeeBuilder.buildMilkFoam(milkFoam);
return mCoffeeBuilder.build();
}
}
构造者模式需要的几种角色我们已经全部实现了,来回顾下:
- Coffee 实际需要的产品
- Builder 制作咖啡过程的抽象
- CoffeeBuilder 具体制作咖啡过程
- CoffeeShopWaiter 使用咖啡制作过程生产咖啡
接下来要做的就是顾客到了店里开始点咖啡
新建顾客类:Consumer.java
/**
* 顾客类
*/
public class Consumer {
public static void main(String[] args) {
//1、消费者来到了咖啡店
CoffeeShopWaiter coffeeShopWaiter = new CoffeeShopWaiter();
//2、消费者讲要一杯拿铁,咖啡店开始生产拿铁咖啡
CoffeeBuilder coffeeBuilder = new CoffeeBuilder();
coffeeShopWaiter.setCoffeeBuilder(coffeeBuilder);
Coffee coffee = coffeeShopWaiter.makeCoffee("拿铁", 50, 150, 50);
//3、生产完,给消费者查看咖啡对不对
System.out.println(coffee.toString());
}
}
制作的咖啡如下:
Coffee{name='拿铁', espresso=50, milkFoam=150, hotMilk=50}
一杯拿铁咖啡已经准备好了,这个时候假如你的朋友想让你带一杯卡布奇诺,你就可以告诉服务员你需要一杯卡布奇诺,然后店员就可以根据你的需要定制出一杯咖啡。
我们知道,卡布奇诺和拿铁的区别就是热牛奶和奶泡比例的区别,
拿铁的热牛奶和奶泡比例大概为3:1
卡布奇诺的热牛奶和奶泡比例大概为1:1
服务员只需要改变下热牛奶和奶泡的量即可,代码如下:
拿铁的:
Coffee coffee = coffeeShopWaiter.makeCoffee("拿铁", 50, 150, 50);
改为
卡布奇诺的:
Coffee coffee = coffeeShopWaiter.makeCoffee("卡布奇诺", 50, 100, 100);
制作的咖啡如下:
Coffee{name='卡布奇诺', espresso=50, milkFoam=100, hotMilk=100}
这样你就可以给你朋友带回去一杯卡布奇诺了。
到这里就完成了一个标准的构造者模式,上面的咖啡店制作咖啡的例子 将咖啡的构建过程和最终的咖啡产品相分离,我们传入不同的参数就可以构建出不同的咖啡,而不用关心内部是怎么去构建的。 这正是构造者模式的特点。
其实这种构建者模式是一个标准的写法,在我们实际的开发中见到的和应用的构造者模式大多都不会是完全按照这种四个角色来编写,更多的采用了构造者模式的演变版本,比如我们使用 StringBuilder 的时候可以通过链式调用生成 String,代码如下:
String string = new StringBuilder().append("123")
.append(45)
.append("67")
.toString();
System.out.println(string);
结果:
1234567
这种链式调用的方式更加符合我们的编码习惯,不断的通过 append 往 StringBuilder 对象中添加内容,最后构建完成以后再通过 toString 返回最终需要的 String 对象,这种方式使用起来非常简单并且不容易出错。
接下来我们也用这种构造者模式的变种去完成咖啡的制作过程:
构造者模式的变种
同样新建具体产品类 Coffee:
public class Coffee {
private String name;
private int espresso;
private int milkFoam;
private int hotMilk;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getEspresso() {
return espresso;
}
public void setEspresso(int espresso) {
this.espresso = espresso;
}
public int getMilkFoam() {
return milkFoam;
}
public void setMilkFoam(int milkFoam) {
this.milkFoam = milkFoam;
}
public int getHotMilk() {
return hotMilk;
}
public void setHotMilk(int hotMilk) {
this.hotMilk = hotMilk;
}
@Override
public String toString() {
return "Coffee{" +
"name='" + name + '\'' +
", espresso=" + espresso +
", milkFoam=" + milkFoam +
", hotMilk=" + hotMilk +
'}';
}
}
这个和前面的 Coffee 类并没有什么不同,接着我们采用静态内部类的方式再创建一个 Coffee 的构造类 CoffeeBuilder 用于具体的 Coffee 的构建过程:
public static class CoffeeBuilder {
private String name;
private int espresso;
private int milkFoam;
private int hotMilk;
public CoffeeBuilder buildName(String name) {
this.name = name;
return this;
}
public CoffeeBuilder buildEspresso(int espresso) {
this.espresso = espresso;
return this;
}
public CoffeeBuilder buildMilkFoam(int milkFoam) {
this.milkFoam = milkFoam;
return this;
}
public CoffeeBuilder buildHotMilk(int hotMilk) {
this.hotMilk = hotMilk;
return this;
}
public Coffee build() {
return new Coffee(this);
}
}
在这个静态内部类中,拥有 Coffee 类的全部属性,在其内部通过不同的构建过程对属性分别赋值,然后返回 CoffeeBuilder 自身,这样可以继续调用 CoffeeBuilder 对象的方法。最终构建完成以后通过 build 方法返回一个具体的 Coffee 对象,接着需要给 Coffee 类提供一个参数为 CoffeeBuilder 的构造方法:
public Coffee(CoffeeBuilder builder) {
this.name = builder.name;
this.espresso = builder.espresso;
this.milkFoam = builder.milkFoam;
this.hotMilk = builder.hotMilk;
}
在 Coffee 的构造方法中,我们通过传入的 CoffeeBuilder 对象,依次给 Coffee 属性进行赋值,这个时候就构建出了一个具体的 Coffee 对象。
通常情况下为了方便使用,会在 Coffee 类的内部提供一个静态的方法返回一个默认的 CoffeeBuilder 对象,便于我们调用 CoffeeBuilder 构建 Coffee,代码如下:
public static CoffeeBuilder newBuilder() {
return new CoffeeBuilder();
}
到这里一个变种的构造者模式已经写完了,全部代码如下:
Coffee.java
public class Coffee {
private String name;
private int espresso;
private int milkFoam;
private int hotMilk;
public static CoffeeBuilder newBuilder() {
return new CoffeeBuilder();
}
public Coffee(CoffeeBuilder builder) {
this.name = builder.name;
this.espresso = builder.espresso;
this.milkFoam = builder.milkFoam;
this.hotMilk = builder.hotMilk;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getEspresso() {
return espresso;
}
public void setEspresso(int espresso) {
this.espresso = espresso;
}
public int getMilkFoam() {
return milkFoam;
}
public void setMilkFoam(int milkFoam) {
this.milkFoam = milkFoam;
}
public int getHotMilk() {
return hotMilk;
}
public void setHotMilk(int hotMilk) {
this.hotMilk = hotMilk;
}
public static class CoffeeBuilder {
private String name;
private int espresso;
private int milkFoam;
private int hotMilk;
public CoffeeBuilder buildName(String name) {
this.name = name;
return this;
}
public CoffeeBuilder buildEspresso(int espresso) {
this.espresso = espresso;
return this;
}
public CoffeeBuilder buildMilkFoam(int milkFoam) {
this.milkFoam = milkFoam;
return this;
}
public CoffeeBuilder buildHotMilk(int hotMilk) {
this.hotMilk = hotMilk;
return this;
}
public Coffee build() {
return new Coffee(this);
}
}
@Override
public String toString() {
return "Coffee{" +
"name='" + name + '\'' +
", espresso=" + espresso +
", milkFoam=" + milkFoam +
", hotMilk=" + hotMilk +
'}';
}
}
接下来,通过消费者类去调用:
public class Consumer {
public static void main(String[] args) {
Coffee coffee = Coffee.newBuilder()
.buildName("拿铁")
.buildEspresso(50)
.buildHotMilk(150)
.buildMilkFoam(50)
.build();
System.out.println(coffee.toString());
}
}
制作的咖啡如下:
Coffee{name='拿铁', espresso=50, milkFoam=50, hotMilk=150}
如果需要卡布奇诺:
public class Consumer {
public static void main(String[] args) {
Coffee coffee = Coffee.newBuilder()
// 改动 传入 卡布奇诺
.buildName("卡布奇诺")
.buildEspresso(50)
// 改动 传入 100
.buildHotMilk(100)
// 改动 传入 100
.buildMilkFoam(100)
.build();
System.out.println(coffee.toString());
}
}
制作的咖啡如下:
Coffee{name='卡布奇诺', espresso=50, milkFoam=100, hotMilk=100}
这种变种模式和上面的标准模式的优劣一眼便知。
实际上,我们使用 Retrofit 和 OkHttp 等开源库的时候,大多都是采用的这种变种的构造者模式去实现的,我之前有写过一篇文章是对 DialogFragment 的封装,也是采用的这种变种的方式,最终通过链式调用来构建弹窗。有兴趣的也可以去看看
地址:一步一步使用 DialogFragment 封装链式调用 Dialog
最后
本文介绍了标准的构造者模式和最常用的变种构造者模式,虽然实现构造者模式需要多写一点代码,在创建我们真正需要的对象的时候,还会产生多余的 Builder 对象,但是构造者模式可以给我们提供良好的封装,使对象的创建和使用相分离,使代码更加清晰,更易使用,易于理解。
好了,就写到这里了,相信你对构建者模式有了一定的了解,接下来就一起来学习下工厂模式吧。
欢迎关注我的公众号: