我们先来看一个生活中的场景, 经过了一上午工作,饥肠辘辘的小王来到了食堂想要饕餮一番,食堂的饭菜着实不错啊,有30道菜,5种水果可供选择,一番纠结后, 小王最终选了一个大鸡腿、一份红烧肉、一碟小青菜、另配米饭和汤。 然后美滋滋的吃了起来。
来分析一下该场景:食堂提供了很多种菜肴和水果(此处假设这些菜品种类长期不变), 而在小王的午饭中,米饭和汤是固定的, 另外他会挑选其中几个菜做为午饭; 将这个场景抽象一下,就是构建一个复杂对象时, 这个对象一些属性是固定不变的,由于需求不确定,其它更多的属性是可选择的;
在这样的场景下, 我们该怎么去用代码实现呢?
方法一: 用构造函数来实现
这个不必多说, 为了实现所有的搭配,我们需要创建大量的构造函数来实现这种需求,即使真的码出了这么多构造函数,万一途中不小心把两个参数的顺序搞混了,编译的时候是不会出错的,但程序运行时就会表现出错误的行为。所以此方法是不可取的。
方法二: 用JavaBean来实现
先来看一下代码:
public class Lunch {
/** 米饭 */
private int rice;
/** 汤 */
private int soup;
/** 鸡腿 */
private int drumstick;
/** 鱼 */
private int fish;
/** 番茄 */
private int tomato;
/** 豆腐 */
private int tofu;
/** 此处省略其它几十样菜品 **/
/** 是否打包 */
private boolean takeout;
/** 此处省略一堆 get&set 方法 **/
public Lunch buildLunch(){
Lunch lunch = new Lunch();
lunch.setRice(1);
lunch.setSoup(1);
lunch.setDrumstick(2);
lunch.setTofu(1);
lunch.setTakeout(false);
return lunch;
}
使用这种方式的好处很明显, 创建对象很容易, 而且代码方便阅读;
但也是有缺点的, 因为构造过程被分到了几个调用中,在构造过程中JavaBean可能处于不一致的状态;需要开发人员花费额外的精力去维护其在多线程情况下的安全性;
方法三: 用创建者模式(Builder Pattern)来实现
顺便提一下,Java 中, 我们经常用到的 StringBuilder 底层使用的就是创建者模式。
先来看一下代码:
public class Lunch {
/**
将成员变量声明为final, 初始化后不可变,这样可保证线程安全;
很多业务场景中,成员变量是需要经常变化的,这样场景下不必声明为final;
*/
/** 米饭 */
private final int rice;
/** 汤 */
private final int soup;
/** 鸡腿 */
private int drumstick;
/** 鱼 */
private int fish;
/** 番茄 */
private int tomato;
/** 豆腐 */
private int tofu;
/** 是否打包 */
private boolean takeout;
public Lunch(LunchBuilder lunchBuilder){
this.rice = lunchBuilder.rice;
this.soup = lunchBuilder.soup;
this.fish = lunchBuilder.fish;
this.drumstick = lunchBuilder.drumstick;
this.tomato = lunchBuilder.tomato;
this.tofu = lunchBuilder.tofu;
this.takeout = lunchBuilder.takeout;
}
public static class LunchBuilder {
/**
* 固定属性
*/
public final int rice;
/**
* 固定属性
*/
public final int soup;
/** 鸡腿 */
public int drumstick;
/** 鱼 */
public int fish;
/** 番茄 */
public int tomato;
/** 豆腐 */
public int tofu;
/** 是否打包 */
public boolean takeout;
public LunchBuilder(int rice, int soup) {
this.rice = rice;
this.soup = soup;
}
public LunchBuilder fish(int fish) {
this.fish = fish;
return this;
}
public LunchBuilder drumstick(int drumstick) {
this.drumstick = drumstick;
return this;
}
public LunchBuilder tomato(int tomato){
this.tomato = tomato;
return this;
}
public LunchBuilder tofu(int tofu){
this.tofu = tofu;
return this;
}
public LunchBuilder takeout(boolean takeout) {
this.takeout = takeout;
return this;
}
/**
* 在调用build方法之前,都不会实例化对象
*/
public Lunch build() {
return new Lunch(this);
}
}
@Override
public String toString() {
return "Meal{" +
"rice='" + rice + '\'' +
", soup='" + soup + '\'' +
", fish='" + fish + '\'' +
", drumstick='" + drumstick + '\'' +
", tomato='" + tomato + '\'' +
", tofu='" + tofu + '\'' +
", takeout=" + takeout +
'}';
}
public static void main(String[] args){
// 按需构建复杂的对象
Lunch lunch1 = new Lunch.LunchBuilder(1, 1).drumstick(2).tofu(2).takeout(Boolean.FALSE).build();
Lunch lunch2 = new Lunch.LunchBuilder(1, 1).fish(1).tomato(1).takeout(Boolean.FALSE).build();
System.out.println(lunch1);
System.out.println(lunch2);
}
}
我们来分析一下上面的代码:
1. 外部类 Lunch 的构造函数, 其参数是它的静态内部类 LunchBuilder ; 静态内部类 LunchBuilder 中的成员变量, 和其外部类 Lunch 的成员变量完全一致;
2. 当构建一顿午饭, 米饭和汤是标配, 可以将其指定为静态内部类LunchBuilder的构造函数的参数, 这样每次构建一顿午饭都少不了要配上米饭和汤; 若是想要某个属性一旦初始化后就不可更改, 可以使用final关键字来修饰;
3. 在 LunchBuilder 中,为每个不是final的成员变量,增加一个方法, 在方法中会为这个变量赋值,然后返回当前的builder;
4. 在 LunchBuilder 中, 有一个build() 方法, 这个方法会调用外部类Lunch的构造函数, 只要最终调用了build方法,才会真正创建一个外部类的对象出来;
文中代码可在此处找到: https://github.com/tiny-v/review_java/tree/master/pattern/src/main/java/com/my/pattern/builder
如有不对的地方, 欢迎各位前来指正。
---- THE END ----