Khái niệm cơ bản về Builder Pattern

Nghiêm Nguyễn Công
VelaCorp
Published in
5 min readDec 24, 2018

Builder Pattern là một mẫu thiết kế thuộc nhóm khởi tạo (Creational Pattern).

Mẫu thiết kế giúp chúng ta có thể tạo ra các đối tượng phức tạp hoặc nhiều đối tượng khác nhau chỉ bằng những câu lệnh đơn giản để tác động nên thuộc tính của nó.

Trong bài viết này tôi xin mời các bạn cùng tìm hiểu về mẫu thiết kế này qua các ví dụ đơn giản :

Khi nào ta cần sử dụng mẫu thiết kế này?

Chúng ta nên sử dụng Builder Pattern khi:

· Khi ta có lớp chứa quá nhiều thuộc tính và cần phải tạo nhiều hàm khởi tạo với số lượng thuộc tính tăng dần.

· Khi ta muốn tạo ra một đối tượng phức tạp, một đối tượng mà có các thuộc tính cố định và thuộc tính tùy chọn.

Builder Pattern được triển khai như thế nào?

Thông thường Builder Pattern có 2 cách triển khai :

Cách 1: Ta sử dụng phương pháp nested class. Chi tiết là ta sẽ xây dựng một class builder trong class chính. Chúng ta sẽ cùng theo dõi ví dụ dưới đây :

· Ta muốn tạo ra đối tượng Computer với các thuộc tính HDD, RAM là cố định, các thuộc tính Card màn hình , bluetooth là thuộc tính tùy chọn.

Ta sẽ xây dựng class Computer như sau:

package builder;

public class Computer {

private String HDD;
private String RAM;

private boolean isGraphicsCardEnabled;
private boolean isBluetoothEnabled;

public String getHDD() {
return HDD;
}

public String getRAM() {
return RAM;
}

public boolean isGraphicsCardEnabled() {
return isGraphicsCardEnabled;
}

public boolean isBluetoothEnabled() {
return isBluetoothEnabled;
}



@Override
public String toString() {
return “Computer{“ +
“HDD=’” + HDD + ‘\’’ +
“, RAM=’” + RAM + ‘\’’ +
“, isGraphicsCardEnabled=” + isGraphicsCardEnabled +
“, isBluetoothEnabled=” + isBluetoothEnabled +
‘}’;
}

Class Computer được xây dựng với các thuộc tính HDD, RAM, isGracphicsCardEnabled, isBluetoothEnabled và các phương thức getter.

Có phương thức toString() để in thông tin.

Tiếp theo, ta xây dựng Class ComputerBuilder là static nest class trong Class Computer :

package builder;public class Computer {    private String HDD;    private String RAM;    private boolean isGraphicsCardEnabled;    private boolean isBluetoothEnabled;    public String getHDD() {
return HDD;
} public String getRAM() { return RAM; } public boolean isGraphicsCardEnabled() { return isGraphicsCardEnabled; } public boolean isBluetoothEnabled() { return isBluetoothEnabled; } private Computer(ComputerBuilder builder) { this.HDD = builder.HDD; this.RAM = builder.RAM; this.isGraphicsCardEnabled = builder.isGraphicsCardEnabled; this.isBluetoothEnabled = builder.isBluetoothEnabled; } @Override public String toString() { return "Computer{" + "HDD='" + HDD + '\'' + ", RAM='" + RAM + '\'' + ", isGraphicsCardEnabled=" + isGraphicsCardEnabled + ", isBluetoothEnabled=" + isBluetoothEnabled + '}'; } public static class ComputerBuilder { private String HDD; private String RAM; private boolean isGraphicsCardEnabled; private boolean isBluetoothEnabled; public ComputerBuilder(String HDD, String RAM) { this.HDD = HDD; this.RAM = RAM; } public ComputerBuilder setGraphicsCardEnabled(boolean isGraphicsCardEnabled) { this.isGraphicsCardEnabled = isGraphicsCardEnabled; return this; } public ComputerBuilder setBluetoothEnabled(boolean isBluetoothEnabled) { this.isBluetoothEnabled = isBluetoothEnabled; return this; } public Computer builder() { return new Computer(this); } }}

Ta thấy rằng Class Computer không có các phương thức setter vì vậy set thuộc tính do class builder làm việc.

Việc set thuộc tính được thực hiện qua hàm khởi tạo có tham số Computer(ComputerBuilder builder), hàm này để private để ngăn việc khởi tạo trực tiếp mà buộc phải khởi tạo thông qua class ComputerBuilder

Xét trong class ComputerBuilder, ta sẽ sử dụng các thuộc tính giống như class chính đó là HDD, RAM, isGraphicsEnabled, isBluetoothEnabled.

Thuộc tính HDD, RAM sẽ được khởi tạo cố định, còn 2 thuộc tính tùy chọn isGraphicsEnabled và isBluetoothEnabled sẽ có phương thức trả về ComputerBuilder để set thuộc tính.

Đặc biệt có phương thức Computer builder() có tác dụng để trả về đối tượng mong muốn.

Ta xây dựng class Demo để test :

import builder.Computer;public class Demo {    public static void main(String[] args) {    Computer comp = new Computer.ComputerBuilder("500 GB", "2 GB").setBluetoothEnabled(true).setGraphicsCardEnabled(true).builder();     System.out.println(comp);    }}

Kết quả:

Computer{HDD=’500 GB’, RAM=’2 GB’, isGraphicsCardEnabled=true, isBluetoothEnabled=true}

Ngoài cách triển khai ở trên, ta còn có thể triển khai Builder Pattern với 4 thành phần cơ bản :

· Product : thành phần này trong bài viết sẽ đại diện cho đối tượng phức tạp phải tạo ra.

· Builder : là thành phần định nghĩa một lớp trừu tượng(abstract class) để tạo một hoặc nhiều thành phần của đối tượng Product.

· ConcreateBuilder : là thành phần triển khai, cụ thể hóa Builder, thành phần sẽ xác định và nắm giữ các thể hiện mà nó tạo ra. Đồng thời nó cũng cung cấp phương thức để trả về các thể hiện mà nó tạo ra trước đó.

· Director: thành phần này sẽ khởi tạo đối tượng Builder.

Chúng ta cùng xem ví dụ về cách triển khai này : Ví dụ ta có chương trình đặt món Burger gồm 2 món Burger rau củ và Burger thịt. Đầu tiên ta xây dựng một interface Item chứa các phương thức loại Burger, giá bán :

Interface Item :

package builder;public interface Item {    public String name();    public float price();}

Tiếp theo, ta có 2 Class MeatClass Vegetable implement Interface Item :

Class Meat :

package builder;public class Meat implements Item {    @Override    public String name() {        return "Chicken Burger";    }    @Override    public double price() {        return 70.5;    }    public void showItem() {        System.out.printf("Name:" + name());        System.out.println("Price:" + price());    }}

Class Vegetable:

package builder;public class Vegetable implements Item {    @Override    public String name() {        return "Veg Burger";    }    @Override    public double price() {        return 25.5;    }    public void showItem() {        System.out.printf("Name:" + name());        System.out.println("Price:" + price());    }}

Trong ví dụ này thì interface Item đại diện cho Product, thể hiện đối tượng phức tạp ta cần tạo ra.

Tiếp theo, ta có Class Meal dùng để kết hợp các thực đơn vào với một danh sách :

package builder;import java.util.ArrayList;import java.util.List;public class Meal {    private List<Item> items = new ArrayList<>();    public void addItem(Item item) {        items.add(item);    }    public void showItems() {        for (Item item : items) {            System.out.println("Item: " + item.name());            System.out.println("Price: " + item.price());        }    }}

Ta xây dựng Class MealBuilder là lớp thực sự chịu trách nhiệm về việc tạo thực đơn món ăn :

package builder;import java.util.ArrayList;import java.util.List;public class Meal {    private List<Item> items = new ArrayList<>();    public void addItem(Item item) {        items.add(item);    }    public void showItems() {        for (Item item : items) {            System.out.println("Item: " + item.name());            System.out.println("Price: " + item.price());        }    }}

Cuối cùng, ta tạo Class Demo để test kết quả :

package builder;public class Demo {    public static void main(String[] args) {        MealBuilder mealBuilder = new MealBuilder();        Meal vegeMeal = mealBuilder.prepareVegMeal();        System.out.println("Veg Meal: ");        vegeMeal.showItems();        System.out.println("------------------------");        Meal meatMeal = mealBuilder.prepareMeat();        System.out.println("Meat Meal: ");        meatMeal.showItems();    }}

Các lợi ích mà Builder Pattern mang lại?

· Builder Pattern cung cấp một cách khởi tạo đối tượng với nhiều thuộc tính phức tạp.

· Hỗ trợ, loại bỏ việc phải viết quá nhiều constructor.

Hy vọng qua các ví dụ đơn giản trên các bạn có thể hiểu thêm về Builder Pattern.

Cám ơn các bạn đã theo dõi bài viết.

--

--