在我们实际的开发需求中,经常需要创建同一类型的不同的对象,而且这个对象的特性还是随机可变的,这样就需要我们使用一种设计模式来满足复杂多变的场景;
1. 业务场景
我们以经常出现的喝咖啡为例,每种咖啡都有大杯小杯和中杯之分,然后咖啡可以添加糖、牛奶或者酒(?,这个口味就比较独特了),我们可以搭配大杯咖啡+糖、大杯咖啡+牛奶、小杯咖啡+酒、大杯+糖+牛奶等等,随着添加的种类的增多,搭配的种类也是不断的呈指数级的变化的,那么该如何使用代码来创建这些搭配呢?
2. Builder
现在来介绍我们今天的主角Builder,这里的Builder不是构建者模式,只是为了通过代码的设计,来实现一个方法链来实现对象的构建,下面通过一段代码来简单的介绍下Builder的用法
我们先看看传统的方式
public class Coffee {
/**
* 咖啡的大小
*/
private int size;
/**
* 是否添加糖
*/
private boolean sugar;
/**
* 是否添加牛奶
*/
private boolean milk;
/**
* 是否添加酒
*/
private boolean wine;
//.... 省略 setter/getter 方法
}
那么传统的方式:
Coffee coffee = new Coffee();
coffee.setSize(1);
coffee.setSugar(Boolean.TRUE);
coffee.setMilk(Boolean.TRUE);
coffee.setWine(Boolean.TRUE);
这样不是不可以 但是一旦我增加了添加的种类,改动的代码就比较多了,而且,这样的代码也显得比较low,没有丝毫美感可言,所以我们这里新增下静态内部类来优化下:
public class Coffee {
/**
* 咖啡的大小
*/
private final int size;
/**
* 是否添加糖
*/
private final boolean sugar;
/**
* 是否添加牛奶
*/
private final boolean milk;
/**
* 是否添加酒
*/
private final boolean wine;
public static class Builder {
private int size;
private boolean sugar;
private boolean milk;
private boolean wine;
public Builder(int size) {
this.size = size;
}
public Builder sugar(boolean sugar) {
this.sugar = sugar;
return this;
}
public Builder milk(boolean milk) {
this.milk = milk;
return this;
}
public Builder wine(boolean wine) {
this.wine = wine;
return this;
}
public Coffee builer() {
return new Coffee(this);
}
}
private Coffee(Builder builder) {
this.size = builder.size;
this.sugar = builder.sugar;
this.milk = builder.milk;
this.wine = builder.wine;
}
public int getSize() {
return size;
}
public boolean isSugar() {
return sugar;
}
public boolean isMilk() {
return milk;
}
public boolean isWine() {
return wine;
}
}
我们这里新增了静态内部类,其内部属性我们这里由于需求需要和父类的中的属性是保持一致的,方便我们在内部类中来操作属性对象的值,然后我们在内部类中提供方法来返回父类的对象,再接着,我们为了不让父类来自己实例化对象,我们将父类的构造器私有化,避免父类通过构建在来创建对象;
这样我们创建对象可以如下进行:
Coffee coffee = new Coffee.Builder(1).milk(Boolean.TRUE).sugar(Boolean.TRUE).builer();
这样我们采用了链式的方式来创建对象,现在一看是不是很简洁了,看着也很舒服了
设计来满足上述需求
Builder方法虽然方便了很多,但是并不能解决我们之前的需求,同时代码量也是增大了很多,现在我们来使用一种设计方法来满足该需求;
该怎么去思考这个问题呢?
- 我们先将咖啡所有可以添加的种类做成一个基类,并且种类作为一个枚举对象
- 在这个基类中我们可以进行种类的添加
- 然后子类中我们只要选择咖啡的大小就可以了
- 子类决定创建对象的类型
按照上面的思路我们来进行代码开发:
public abstract class Coffee {
/**
* Coffee定义可以添加的种类
* 这里我们为了程序的演示方便,我们这里定义成枚举
* 在实际的工作中,我们可以定义成一个种类的基类,利用多态的思想来实现
*/
public enum Categorie {
SUGAR,
MILK,
WINE
}
/**
* 定义个Set集合来存储添加种类
*/
final Set<Categorie> categories;
/**
* 定义一个构建器 并且约束该构建器的类型
* 允许方法链在子类中运行正常,不需要进行类型强转
*
* @param <T>
*/
abstract static class Builder<T extends Coffee.Builder<T>> {
/**
* 将Pizza所有的类型属性枚举清空
*/
EnumSet<Coffee.Categorie> categories = EnumSet.noneOf(Coffee.Categorie.class);
/**
* 新增类型枚举 并且由子类返回对应的Pizza类型
*
* @param categorie
* @return
*/
public T addCategories(Coffee.Categorie categorie) {
categories.add(Objects.requireNonNull(categorie));
return self();
}
/**
* 构建器,由子类去实现构建方法
*
* @return
*/
abstract Coffee build();
/**
* 由子类来实现 返回子类的对象
*
* @return
*/
protected abstract T self();
}
/**
* Coffee 基类构造器
*
* @param builder
*/
Coffee(Coffee.Builder<?> builder) {
categories = builder.categories.clone();
}
}
上面我们定义了Coffee基类,实现了我们之前思考的第一点,在基类中将需要添加的种类通过Builder定义了添加子类的方法,那么子类该如何定义呢?
public class SizeCoffee extends Coffee {
/**
* 定义咖啡的大小
*/
public enum Size {
SMALL,
LARGE,
MEDIUM
}
private final SizeCoffee.Size size;
/**
* 定义静态内部类来实现对象的创建
*/
public static class Builder extends Coffee.Builder<Builder> {
private final SizeCoffee.Size size;
public Builder(SizeCoffee.Size size) {
this.size = size;
}
/**
* 返回子类对象
*
* @return
*/
@Override
Coffee build() {
return new SizeCoffee(this);
}
@Override
protected SizeCoffee.Builder self() {
return this;
}
}
/**
* LargeCoffee构造器
* 构造器私有化,为了防止通过父类来创建对象对象
*
* @param builder
*/
private SizeCoffee(Builder builder) {
super(builder);
this.size = builder.size;
}
}
这样子类也创建好了,我们可以通过代码来创建
Coffee coffee = new SizeCoffee.Builder(SizeCoffee.Size.LARGE)
.addCategories(Coffee.Categorie.MILK)
.addCategories(Coffee.Categorie.SUGAR).build();
这样的话,以后计算有什么添加种类或者咖啡的包装方式变换了,我们都可以基于该设计来实现我们的需求