建造者模式 (BuilderPattern) ,不直接生成想要的对象,而让客户端利用所有必要的参数调用构造器,得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成通常是不可变的对象。
平行层结构
public class NutritionFacts {
private final int servingSize;//必须参数
private final int servings;//必须参数
private final int calories;//可选参数
private final int fat;//可选参数
private final int sodium;//可选参数
private final int carbohydrate;//可选参数
public static class Builder {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int calories) {
this.calories = calories;
return this;
}public Builder fat(int fat) {
this.fat = fat;
return this;
}public Builder sodium(int sodium) {
this.sodium = sodium;
return this;
}public Builder carbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
return this;
}
public NutritionFacts build(){
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
注意NutritionFacts是不可变的,所有的默认参数值都单独放在一个地方。builder的设值方法返回builder本身,以便把调用链接起来,得到一个流式的API。
客户端调用例子
public class Main {
@Test
public void test(){
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
.calories(100)
.sodium(35)
.carbohydrate(27)
.build();
}
}
类层次结构
用类层次根部的一个抽象类表示各式各样的比萨(Pizza)。
public abstract class Pizza {
public enum Topping {HAM,MUSHROOM,ONION,PEPPER,SAUSAGE}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping){
toppings.add(Objects.requireNonNull(topping));
return self();//模拟的self类型
}
abstract Pizza build();
// Subclasses must override this method to return "this"
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}
注意,Pizza.Builder的类型是泛型,带有一个递归类型参数。它和抽象的self方法一样,允许在子类中适当地进行方法链接,不需要转换类型。
需要尺寸参数的比萨:size
public class NyPizza extends Pizza {
public enum Size{SMALL,MEDIUM,LARGE}
private final Size size;
public static class Builder extends Pizza.Builder<Builder> {
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override
public NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
}
需要指定酱汁是内置还是外置的比萨:sauceInside
public class Calzone extends Pizza {
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder> {
private boolean sauceInside = false; // Default
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
public Calzone build() {
return new Calzone(this);
}
@Override
protected Builder self() {
return this;
}
}
private Calzone(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
}
客户端
public class Main {
@Test
public void test(){
NyPizza nyPizza = new NyPizza.Builder(SMALL)
.addTopping(SAUSAGE)
.addTopping(ONION)
.build();
Calzone calzone = new Calzone.Builder()
.addTopping(HAM)
.sauceInside()
.build();
System.out.println(nyPizza);
System.out.println(calzone);
}
}
Spring中如何使用设计模式
Builder模式
以下内容转自https://my.oschina.net/zhangxufeng/blog/305342
关于Builder模式,我想使用过lombok的同学肯定会说builder模式非常的简单,只需要在某个bean上使用@Builder注解进行声明即可,lombok可以自动帮我们将其声明为一个Builder的bean。关于这种使用方式,本人不置可否,不过就我的理解,这里主要有两个点我们需要理解:
Builder模式就其名称而言,是一个构建者,我更倾向于将其理解为通过一定的参数,通过一定的业务逻辑来最终生成某个对象。如果仅仅只是使用lombok的这种方式,其本质上也还是创建了一个简单的bean,这个与通过getter和setter方式构建一个bean是没有什么大的区别的;
在Spring框架中,使用设计模式最大的问题在于如果在各个模式bean中能够注入Spring的bean,如果能够注入,那么将大大的扩展其使用方式。因为我们就可以真的实现通过传入的简单的几个参数,然后结合Spring注入的bean进行一定的处理后,以构造出我们所需要的某个bean。显然,这是lombok所无法实现的;
关于Builder模式,我们可以以前面奖励发放的SendPrizeRequest的构造为例进行讲解。在构造request对象的时候,必然是通过前台传如的某些参数来经过一定的处理,最后生成一个request对象。那么我们就可以使用Builder模式来构建一个SendPrizeRequest。这里假设根据前台调用,我们能够获取到prizeId和userId,那么我们就可以创建一个如下的SendPrizeRequest:
public class SendPrizeRequest {
private final PrizeTypeEnum prizeType;
private final int amount;
private final String userId;
public SendPrizeRequest(PrizeTypeEnum prizeType, int amount, String userId) {
this.prizeType = prizeType;
this.amount = amount;
this.userId = userId;
}
@Component
@Scope("prototype")
public static class Builder {
@Autowired
PrizeService prizeService;
private int prizeId;
private String userId;
public Builder prizeId(int prizeId) {
this.prizeId = prizeId;
return this;
}
public Builder userId(String userId) {
this.userId = userId;
return this;
}
public SendPrizeRequest build() {
Prize prize = prizeService.findById(prizeId);
return new SendPrizeRequest(prize.getPrizeType(), prize.getAmount(), userId);
}
}
public PrizeTypeEnum getPrizeType() {
return prizeType;
}
public int getAmount() {
return amount;
}
public String getUserId() {
return userId;
}
}
这里就是使用Spring维护一个Builder模式的示例,具体的 维护方式就是在Builder类上使用@Component和@Scope注解来标注该Builder类,这样我们就可以在Builder类中注入我们所需要的实例来进行一定的业务处理了。关于该模式,这里有几点需要说明:
在Builder类上必须使用@Scope注解来标注该实例为prototype类型,因为很明显,我们这里的Builder实例是有状态的,无法被多线程共享;
在Builder.build()方法中,我们可以通过传入的参数和注入的bean来进行一定的业务处理,从而得到构建一个SendPrizeRequest所需要的参数;
Builder类必须使用static修饰,因为在Java中,如果内部类不用static修饰,那么该类的实例必须依赖于外部类的一个实例,而我们这里本质上是希望通过内部类实例来构建外部类实例,也就是说内部类实例存在的时候,外部类实例是还不存在的,因而这里必须使用static修饰;
根据标准的Builder模式的使用方式,外部类的各个参数都必须使用final修饰,然后只需要为其声明getter方法即可。
上面我们展示了如何使用Spring的方式来声明一个Builder模式的类,那么我们该如何进行使用呢,如下是我们的一个使用示例:
@Service
public class ApplicationService {
@Autowired
private PrizeSenderFactory prizeSenderFactory;
@Autowired
private ApplicationContext context;
public void mockedClient() {
SendPrizeRequest request = newPrizeSendRequestBuilder()
.prizeId(1)
.userId("u4352234")
.build();
PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);
prizeSender.sendPrize(request);
}
public Builder newPrizeSendRequestBuilder() {
return context.getBean(Builder.class);
}
}
上述代码中,我们主要要看一下newPrizeSendRequestBuilder()方法,在Spring中,如果一个类是多例类型,也即使用@Scope(“prototype”)进行了标注,那么每次获取该bean的时候就必须使用ApplicationContext.getBean()方法获取一个新的实例,至于具体的原因,读者可查阅相关文档。我们这里就是通过一个单独的方法来创建一个Builder对象,然后通过流式来为其设置prizeId和userId等参数,最后通过build()方法构建得到了一个SendPrizeRequest实例,通过该实例来进行后续的奖励发放。