![translation](https://cdn.durumis.com/common/trans.png)
นี่คือโพสต์ที่แปลด้วย AI
[Effective Java] รายการที่ 2. พิจารณาตัวสร้างหากมีพารามิเตอร์มากมาย
- ภาษาที่เขียน: ภาษาเกาหลี
- •
-
ประเทศอ้างอิง: ทุกประเทศ
- •
- เทคโนโลยีสารสนเทศ
เลือกภาษา
สรุปโดย AI ของ durumis
- แนะนำรูปแบบต่างๆ สำหรับการจัดการตัวสร้างที่มีพารามิเตอร์มากมายอย่างมีประสิทธิภาพ โดยการเปรียบเทียบข้อดีและข้อเสียของรูปแบบตัวสร้างแบบทีละขั้น รูปแบบ Java Beans และรูปแบบตัวสร้าง
- รูปแบบตัวสร้างมีข้อดีคือช่วยเพิ่มความสามารถในการอ่านและความเสถียรของโค้ด โดยเฉพาะอย่างยิ่งเมื่อใช้ร่วมกับคลาสที่ออกแบบเป็นแบบลำดับชั้น จะยิ่งมีประสิทธิภาพมากขึ้น
- รูปแบบตัวสร้างช่วยให้คุณเขียนโค้ดได้ชัดเจนขึ้น แต่ก็มีข้อเสียคือการสร้างวัตถุตัวสร้างและโค้ดที่ซับซ้อน
รูปแบบตัวสร้างแบบลำดับชั้น
ทั้งโรงงานแบบคงที่และตัวสร้างมีปัญหาในการจัดการกับพารามิเตอร์จำนวนมากอย่างเหมาะสม ตัวอย่างเช่น หากคลาสมี 6 ฟิลด์ และคุณต้องการแบ่งตัวสร้างออกเป็นกรณีที่มี 2 พารามิเตอร์ 3 พารามิเตอร์ ... เป็นต้น คุณสามารถใช้รูปแบบตัวสร้างแบบลำดับชั้น ดังที่แสดงด้านล่าง
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 NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0, 0);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
แต่แม้ว่าจะมีพารามิเตอร์มากขึ้นก็ตาม เมื่ออ่านรหัส คุณอาจสับสนกับความหมายของแต่ละค่าและอาจสับสนกับพารามิเตอร์ที่มีประเภทเหมือนกัน และใส่ค่าที่ไม่ถูกต้อง
รูปแบบ JavaBeans
รูปแบบ JavaBeans คือการสร้างวัตถุโดยใช้ตัวสร้างที่ไม่มีพารามิเตอร์และเรียกใช้เมธอด setter เพื่อตั้งค่าค่าของพารามิเตอร์ ที่ต้องการ
public class NutritionFactsWithJavaBeansPattern {
private int servingSize = -1; // จำเป็น ไม่มีค่าเริ่มต้น
private int servings = -1; // จำเป็น ไม่มีค่าเริ่มต้น
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public void setServingSize(int servingSize) {
this.servingSize = servingSize;
}
public void setServings(int servings) {
this.servings = servings;
}
public void setCalories(int calories) {
this.calories = calories;
}
public void setFat(int fat) {
this.fat = fat;
}
public void setSodium(int sodium) {
this.sodium = sodium;
}
public void setCarbohydrate(int carbohydrate) {
this.carbohydrate = carbohydrate;
}
รูปแบบ JavaBeans ช่วยให้คุณสร้างอินสแตนซ์โดยไม่สับสนกับค่าแม้จะมีพารามิเตอร์มากขึ้น อย่างไรก็ตาม คุณต้องเรียกใช้เมธอด setter หลายตัวเพื่อสร้างวัตถุเดียว และความสอดคล้องจะหายไปจนกว่าวัตถุจะเสร็จสมบูรณ์ ดังนั้นคลาสจึงไม่สามารถทำให้อยู่ใน สภาพที่ไม่เปลี่ยนแปลงได้
รูปแบบตัวสร้าง
รูปแบบตัวสร้างมักจะใช้เป็นการผสมผสานระหว่างความเสถียรของรูปแบบตัวสร้างแบบลำดับชั้นและความสามารถในการอ่าน ของรูปแบบ JavaBeans
ไคลเอ็นต์ไม่ได้สร้างวัตถุโดยตรง แต่เรียกใช้ตัวสร้างโดยใช้พารามิเตอร์ที่จำเป็นเท่านั้นเพื่อรับวัตถุตัวสร้าง จากนั้น วัตถุตัวสร้างจะให้เมธอด setter ที่คล้ายกับเมธอด setter เพื่อตั้งค่าพารามิเตอร์แบบเลือก สุดท้าย เรียกใช้เมธอด build() ที่ไม่มีพารามิเตอร์เพื่อรับวัตถุที่ต้องการ
public class NutritionFactsWithBuilderPattern {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
private NutritionFactsWithBuilderPattern(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
public static class Builder {
private final int servingSize;
private final int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
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) {
calories = val;
return this;
}
public NutritionFactsWithBuilderPattern build() {
return new NutritionFactsWithBuilderPattern(this);
}
}
ตัวสร้างภายในคลาส Builder จะรับเฉพาะพารามิเตอร์ที่จำเป็นเท่านั้น และพารามิเตอร์แบบเลือกจะถูกเติมด้วยเมธอด setter แบบหนึ่ง จากนั้น NutritionFactsWithBuilderPattern วัตถุที่เสร็จสมบูรณ์จะถูกสร้างขึ้นโดยใช้เมธอด build() NutritionFactsWithBuilderPattern คลาสเป็นแบบไม่เปลี่ยนแปลง และเนื่องจากเมธอด setter ของตัวสร้างจะส่งคืนตัวสร้าง เอง จึงสามารถเรียกใช้แบบเชื่อมโยงกันได้ วิธีนี้เรียกว่า fluent API หรือ method chaining
NutritionFactsWithBuilderPattern nutritionFacts =
new NutritionFactsWithBuilderPattern.Builder(240, 8)
.calories(100)
.sodium(35)
สำหรับไคลเอ็นต์ รูปแบบตัวสร้างช่วยให้คุณเขียนและอ่านรหัสได้ง่าย
รูปแบบตัวสร้างทำงานได้ดีกับคลาสที่ออกแบบตามลำดับชั้น
public abstract class Pizza {
public enum Topping {
HAM, MUSHROOM, ONION, PEPPER, SAUSAGE
}
final Set toppings;
Pizza(Builder> builder) {
toppings = builder.toppings.clone();
}
abstract static class Builder> {
private EnumSet toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
toppings.add(topping);
return self();
}
abstract Pizza build();
protected abstract T self();
}
Pizza.Builder คลาสเป็นประเภททั่วไปที่ใช้การจำกัดประเภทแบบวนซ้ำและใช้เมธอด self() แบบนามธรรมเพื่อให้การสนับสนุน method chaining โดยไม่ต้องแปลงประเภทในคลาสย่อย คลาสย่อยควรส่งคืนค่าของเมธอดนามธรรมนี้ให้เป็นตัวมันเอง
ตอนนี้ ลองมาดูคลาสย่อยของ Pizza นั่นคือ พิซซ่าแบบนิวยอร์กและพิซซ่าแบบ Calzone และสัมผัสกับความยืดหยุ่นของรูปแบบตัวสร้าง
public class NyPizza extends Pizza {
public enum Size {
SMALL, MEDIUM, LARGE
}
private final Size size; // พารามิเตอร์ที่จำเป็น
private NyPizza(Builder builder) {
super(builder);
size = builder.size;
}
public static class Builder extends Pizza.Builder {
private final Size size;
public Builder(Size size) {
this.size = size;
}
@Override
NyPizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
}
public class CalzonePizza extends Pizza {
private final boolean sauceInside; // พารามิเตอร์แบบเลือก
private CalzonePizza(Builder builder) {
super(builder);
sauceInside = builder.sauceInside;
}
public static class Builder extends Pizza.Builder {
private boolean sauceInside = false;
public Builder sauceInside() {
sauceInside = true;
return this;
}
@Override
CalzonePizza build() {
return new CalzonePizza(this);
}
@Override
protected Builder self() {
return this;
}
}
เมธอด build() ที่กำหนดไว้ในตัวสร้างของแต่ละคลาสย่อยจะส่งคืนคลาสย่อยที่เฉพาะเจาะจง ฟังก์ชันที่เมธอดของคลาสย่อยจะส่งคืน ประเภทย่อยแทนที่จะเป็นประเภทของเมธอดของคลาสระดับบนเรียกว่า covariant return typing โดยใช้ฟังก์ชันนี้ ไคลเอ็นต์ ไม่จำเป็นต้องแปลงประเภท
NyPizza nyPizza = new NyPizza.Builder(Size.SMALL)
.addTopping(Topping.SAUSAGE)
.addTopping(Topping.ONION)
.build();
CalzonePizza calzonePizza = new CalzonePizza.Builder()
.addTopping(Topping.HAM)
.sauceInside()
สำหรับไคลเอ็นต์ สามารถใช้ enum ของ Pizza และ enum ของแต่ละคลาสย่อยร่วมกันได้ และสร้างวัตถุให้สมบูรณ์โดยใช้ เมธอดที่เหมาะสม
ข้อเสียของรูปแบบตัวสร้าง
- คุณต้องสร้างวัตถุตัวสร้าง
- รหัสมีขนาดใหญ่
สรุป
หากตัวสร้างหรือเมธอดโรงงานแบบคงที่ต้องจัดการกับพารามิเตอร์จำนวนมาก ให้พิจารณาใช้รูปแบบตัวสร้าง
แหล่งที่มา
- Effective Java