Builder Pattern ใน Java
วันนี้เรามารู้จักกับ Builder Pattern กัน โดยเราจะใช้ Java เป็นตัวอย่างประกอบ เจ้า Design Pattern นี้ใช้สำหรับประยุกต์กับการสร้างออบเจกต์ Model หรือ Entity โดยเฉพาะครับ ว่าแล้วไปดูกันเลย
บ่อยครั้งที่ออบเจกต์ในบทบาท Model หรือ Entity มักประกอบไปด้วยแอททริบิวท์มากมาย อาทิเช่น ออบเจกต์ Customer
ประกอบไปด้วยแอททริบิวท์ id
, firstname
, lastname
และอื่นๆอีกมากมาย เป็นต้น และในการออกแบบคลาสของเรา เราก็ใส่คอนเซ็ปต์ Information Hiding และ Encapsulation เข้าไปอีก ทำให้โค้ดที่เรียกใช้นั้นเยินเย้อ ว่าแล้วลองมาดูตัวอย่างกัน ตัวย่างที่ 1 (Customer.java แบบ Conventional) แสดงโค้ดตัวอย่าง
package conventional;
public class Customer {
private int id;
private String firstname;
private String lastname;
private String address;
private String email;
private String phone;
public Customer(){
}
public Customer(int id,String firstname,String lastname,String address,String email,String phone) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
this.address = address;
this.email = email;
this.phone = phone;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
} // end of class
จากตัวอย่างข้างต้น จะเห็นได้ว่านี่คือรูปแบบที่เราทำกันมาปกติ ซึ่งในการเรียกใช้งานนั้นค่อนข้างยุ่งยากในการเขียนโค้ด ตัวอย่างข้างใต้นี้คือตัวอย่างการเรียกใช้ การเรียกใช้ผ่านเมธอด setter
ซึ่งต้องพิมพ์เยอะ ส่วนการเรียกใช้ผ่าน Constructor ก็มีโอกาสสลับตำแหน่งของข้อมูลได้ อีกทั้งในความเป็นจริงแอททริบิวท์อาจมีมากกว่านี้ก็เป็นได้ :(
Customer cust = new Customer();
cust.setId(1);
cust.setFirstname("James");
cust.setLastname("Pat");
cust.setAddress("Rama 3 Bangkok");
cust.setEmail("james@gmail.com");
cust.setPhone("081-123-4567");
// or
Customer cust2 = new Customer(1,"James","Pat","Rama 3 Bangkok","james@gmail.com","081-123-4567");
มันจะดีกว่ามั้ยถ้าเราสามารถโหลดออบเจกต์ Customer
ในรูปแบบตามตัวอย่างข้างใต้นี้
Customer cust = new Customer.Builder()
.id(1)
.firstname("James")
.lastname("Pat")
.address("Rama 3 Bangkok")
.email("james@gmail.com")
.phone("081-123-4567")
.build();
ซึ่งรูปแบบการเขียนลักษณะนี้หรือออกแบบคลาสแบบนี้ เราเรียกว่า Builder Pattern ครับ จะเห็นได้ว่ากระชับขึ้น แล้วเราจะออกแบบคลาสของเราอย่างไรหล่ะให้สามารถถูกเรียกใช้แบบนี้ได้ ไปดูซอร์สโค้ดของคลาส Customer
ใหม่นี้กัน ตัวอย่างที่ 2 (Customer.java แบบ Builder) แสดงโค้ดตัวอย่าง
package builder;
public class Customer {
private int id;
private String firstname;
private String lastname;
private String address;
private String email;
private String phone;
public Customer() {
}
private Customer(Customer.Builder builder){
id = builder.id;
firstname = builder.firstname;
lastname = builder.lastname;
address = builder.address;
email = builder.email;
phone = builder.phone;
} // end of Customer(builder)
public static class Builder{
private int id;
private String firstname;
private String lastname;
private String address;
private String email;
private String phone;
public Customer.Builder id(int id){
this.id = id;
return this;
}
public Customer.Builder firstname(String firstname){
this.firstname = firstname;
return this;
}
public Customer.Builder lastname(String lastname){
this.lastname = lastname;
return this;
}
public Customer.Builder address(String address){
this.address = address;
return this;
}
public Customer.Builder email(String email){
this.email = email;
return this;
}
public Customer.Builder phone(String phone){
this.phone = phone;
return this;
}
public Customer build(){
return new Customer(this);
}
} // end of Builder class
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String toString(){
return id + ", " + firstname + " " + lastname + ", " + address +
", " + email + ", " + phone;
}
} // end of class
(OH NO!!! ทำไมมันยาวกว่าเดิมครับ? ตอนเรียกใช้หน่ะมันง่ายขึ้น แต่ลำบากตอนประกาศคลาส คิดซะว่าลำบากทีเดียวครับ) มาดูจุดที่น่าสนใจของตัวอย่างข้างต้นกัน โปรดสังเกตบรรทัดที่ 24 ว่า มี static
อินเนอร์คลาส Builder
อยู่
public static class Builder{
. . .
}
ตรงนี้แหละที่ทำให้เราเรียกคำสั่ง new Customer.Builder()
ได้ สรุปคือเรา new
คลาส Builder
นะ ไม่ใช่ Customer
และเจ้าคลาส Builder
นี้ก็ประกอบไปด้วยแอททริบิวท์เฉกเช่นเดียวกับคลาส Customer
เลย นอกจากนี้คลาส Builder
ยังประกอบไปด้วยเมธอดชื่อเดียวกับแอททริบิวท์อีกด้วย เมธอดเหล่านี้แหละที่ทำหน้าที่แทนเมธอด setter
แบบเก่า แต่มันใช้เทคนิคการรีเทิร์นตัวมันเอง ทำให้เราสามารถเรียกใช้แบบ Method Chanining ได้ เช่น .id().firstname().lastname()
ต่อกันไปได้เรื่อยๆ เป็นต้น แล้วสุดท้ายเราก็เรียกเมธอด build()
เพื่อเป็นการรีเทิร์นออบเจกต์ชนิด Customer
ที่ต้องการออกมา (เรา new Customer(this)
แล้วส่ง builder
เข้าไป ดังนั้นจึงต้องเตรียม Constructor รองรับรูปแบบนี้ด้วย) ไม่ยากใช่ไหมครับ นี่แหละสาเหตุที่เขาตั้งชื่อว่า Builder Pattern คือรูปแบบการเขียนหรือออกแบบโปรแกรมให้มัน Build ออบเจกต์ออกมาในแบบที่กระชับ ซึ่งโดยมากเราก็ประยุกต์ใช้กับออบเจกต์ Model ตัวอย่างที่ยกมานี้เป็นตัวอย่างแบบง่ายๆ ในการทำงานจริงคุณผู้อ่านก็ต้องไปประยุกต์ตามสถานการณ์และความเหมาะสมด้วย Pattern มันไม่ได้ตายตัว ขอเพียงคอนเซ็ปท์อย่าเพี้ยนก็ใช้ได้ :)