02.当构造方法参数过多时使用builder模式
多个参数时使用Builder替代构造方法
原文笔记
假设存在一个对象,在构造这个对象时需要传入多个参数,而且这些参数没有明显的区分特征。在这种情况下,可以有两种实现方法:第一种通过构造方法传入所需的参数;第二种方法可以创建一个空的对象,然后通过调用对象的setter方法设置参数。
针对第一种方法,可能会出现如下的调用代码:
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
这里存在两个问题:第一,我们可能不需要一次传入这么多数据。如这里,可能0这个变量不是我们所需要的,但是为了适配方法的参数列表,我们必须为其设置一个无意义的值。第二,针对这里的每一个数据,在调用方看来不能直观的感受每一个数据的含义。如这里假设调用时将前两个参数位置颠倒了,在编译器看来并不会有任何为题,但是实际业务逻辑已经发生了改变。
针对于第二种方法,先创建一个空的对象,再通过调用对象的setter方法设置数据。
public class NutritionFacts {
// Parameters initialized to default values (if any)
private int servingSize = -1; // Required; no default value
private int servings = -1; // Required; no default value
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() { }
// Setters
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
}
这种方式可以避免前面参数性质不清晰的问题,且可以灵活的配置所需要的参数。但是这里可能存在另外一个问题,由于setter调用没有强制性,因此可能存在在对象组装成功之前被调用,这种情况下会出现对象的逻辑与预期的逻辑不一致的问题。针对以上两个问题,引入Builder模式。
Builder实现代码如下:
package com.cs.lock;
// Builder Pattern
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 val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
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 nutritionFacts = 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
toppings; abstract static class Builder > { EnumSet toppings = EnumSet.noneOf(Topping.class); public T addTopping(Topping topping) { toppings.add(Objects.requireNonNull(topping)); return self(); } abstract Pizza build(); // Subclasses must override this method to return "this" protected abstract T self(); } Pizza(Builder> builder) { toppings = builder.toppings.clone(); // See Item 50 } } -
NyPizza
public class NyPizza extends Pizza { public enum Size {SMALL, MEDIUM, LARGE} private final Size size; public static class Builder extends Pizza.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; } } -
Calzone
public class Calzone extends Pizza { private final boolean sauceInside; public static class Builder extends Pizza.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; } }
实现代码
假设需要封装一个请求对象,该请求对象由Header、Body两部分组成。在实现代码中,可以在build方法中针对字段属性进行非空或其他逻辑校验,这里不做实现。使用Builder实现如下:
-
RequestMessageHeader:
public class RequestMessageHeader { private String version; private String encoding; private String intfCode; private RequestMessageHeader(Builder builder) { this.version = builder.version; this.encoding = builder.encoding; this.intfCode = builder.intfCode; } public static class Builder{ private final String intfCode; private String version; private String encoding; public Builder(String intfCode) { this.intfCode = intfCode; } public Builder version(String version) { this.version = version; return this; } public Builder encoding(String encoding) { this.encoding = encoding; return this; } public RequestMessageHeader build(){ return new RequestMessageHeader(this); } } }
-
RequestMessageBody:
public class RequestMessageBody { private final String body; public RequestMessageBody(Builder builder) { this.body = builder.body; } public static class Builder { private final String body; public Builder(String body) { this.body = body; } public RequestMessageBody build() { return new RequestMessageBody(this); } } }
-
RequestMessage:
public class RequestMessage { private RequestMessageHeader header; private RequestMessageBody body; private RequestMessage(Builder builder) { this.header = builder.header; this.body = builder.body; } public static class Builder { private final RequestMessageHeader header; private final RequestMessageBody body; public Builder(RequestMessageHeader header, RequestMessageBody body) { this.header = header; this.body = body; } public RequestMessage build() { return new RequestMessage(this); } } }