Mẫu Builder
Thuộc phần Lập trình hướng đối tượng trong hành trình Java của Coddy. Bài học 67 trên 87.
Mẫu Builder là một mẫu thiết kế khởi tạo giúp giải quyết vấn đề phổ biến: tạo các đối tượng phức tạp với nhiều tham số tùy chọn. Thay vì tạo nhiều hàm khởi tạo hoặc truyền null cho các tham số không dùng đến, Builder cho phép bạn tạo đối tượng từng bước.
Hãy hình dung bạn cần tạo một đối tượng User với các trường bắt buộc (name, email) và các trường tùy chọn (age, phone, address). Nếu không dùng Builder, bạn sẽ cần các hàm khởi tạo lồng nhau hoặc một hàm khởi tạo rườm rà với nhiều tham số. Mẫu Builder mang đến cách làm gọn gàng hơn:
public class User {
private final String name; // bắt buộc
private final String email; // bắt buộc
private final int age; // không bắt buộc
private final String phone; // không bắt buộc
private User(UserBuilder builder) {
this.name = builder.name;
this.email = builder.email;
this.age = builder.age;
this.phone = builder.phone;
}
public static class UserBuilder {
private final String name;
private final String email;
private int age;
private String phone;
public UserBuilder(String name, String email) {
this.name = name;
this.email = email;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public User build() {
return new User(this);
}
}
}Các phương thức của Builder trả về this, cho phép nối chuỗi phương thức (method chaining): một cách liền mạch, dễ đọc để tạo đối tượng:
User user = new User.UserBuilder("Alice", "alice@email.com")
.age(25)
.phone("555-1234")
.build();Lưu ý rằng hàm khởi tạo của User là riêng tư: bạn chỉ có thể tạo đối tượng thông qua Builder. Mẫu này đặc biệt hữu ích khi đối tượng có nhiều trường tùy chọn, giúp code dễ đọc hơn và ít lỗi hơn so với danh sách tham số dài.
Thử thách
DễHãy cùng xây dựng hệ thống đặt pizza bằng mẫu thiết kế Builder (Builder Pattern)! Thay vì dùng một hàm khởi tạo có hàng tá tham số cho mọi loại nhân và tùy chọn có thể có, bạn sẽ tạo một builder linh hoạt, giúp tùy chỉnh pizza rõ ràng và dễ đọc.
Bạn sẽ sắp xếp code trong hai tệp:
Pizza.java: Tạo lớpPizzasử dụng mẫu thiết kế Builder để khởi tạo. Pizza của bạn cần có các trường sau:size(String): bắt buộc, đại diện cho kích cỡ pizzacheese(boolean): không bắt buộc, mặc định là falsepepperoni(boolean): không bắt buộc, mặc định là falsemushrooms(boolean): không bắt buộc, mặc định là falseHàm khởi tạo của
Pizzaphải là private và chỉ nhậnPizzaBuilderlàm tham số, sau đó sao chép mọi giá trị từ builder vào các trường của pizza.Thêm phương thức
getDescription()trả về một chuỗi theo định dạng này:[size] Pizza with: [toppings], trong đó toppings là danh sách các loại nhân được chọn, phân tách bằng dấu phẩy (Cheese, Pepperoni, Mushrooms). Nếu không chọn loại nhân nào, phần toppings chỉ cần ghino toppings.Tạo lớp bên trong static
PizzaBuildergồm:- Hàm khởi tạo nhận tham số
sizebắt buộc - Các phương thức
cheese(),pepperoni()vàmushrooms(), mỗi phương thức đặt giá trị boolean tương ứng thành true và trả vềthisđể có thể gọi nối tiếp - Phương thức
build()trả về một đối tượngPizzamới
- Hàm khởi tạo nhận tham số
Main.java: Hãy minh họa cách hoạt động của pizza builder! Bạn sẽ nhận bốn đầu vào: kích cỡ pizza (String) và ba giá trị có/không (String) cho biết lần lượt có thêm cheese, pepperoni và mushrooms hay không.Tạo một
PizzaBuildervới kích cỡ đã cho. Sau đó, dựa trên từng đầu vào có/không, gọi nối tiếp các phương thức thêm nhân phù hợp. Nếu đầu vào là"yes", hãy gọi phương thức thêm nhân tương ứng; nếu là"no", hãy bỏ qua phương thức đó. Cuối cùng, gọibuild()để tạo pizza.In kết quả gọi
getDescription()trên pizza vừa tạo.
Bạn sẽ nhận lần lượt bốn đầu vào: size (String), add cheese (String: "yes" hoặc "no"), add pepperoni (String: "yes" hoặc "no"), add mushrooms (String: "yes" hoặc "no").
Ví dụ, với các đầu vào Large, yes, no, yes, đầu ra của bạn sẽ là: Large Pizza with: Cheese, Mushrooms
Hãy chú ý cách mẫu thiết kế Builder cho phép bạn tạo pizza với mọi tổ hợp nhân mà không cần viết hàm khởi tạo riêng cho từng trường hợp!
Tự làm thử
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Đọc các giá trị đầu vào
String size = scanner.nextLine();
String addCheese = scanner.nextLine();
String addPepperoni = scanner.nextLine();
String addMushrooms = scanner.nextLine();
// TODO: Tạo PizzaBuilder với kích cỡ đã cho
// TODO: Gọi nối tiếp các phương thức thêm nguyên liệu dựa trên đầu vào yes/no
// TODO: Tạo bánh pizza và in ra mô tả của bánh
}
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Kiến thức cơ bản về lập trình hướng đối tượng
Tệp bên ngoàiGiới thiệu về lập trình hướng đối tượngLớp và đối tượngTừ khóa thisPhương thứcTrường (thuộc tính)Phương thức khởi tạoNạp chồng phương thức khởi tạoÔn tập: Máy tính đơn giản4Kế thừa
Kế thừa cơ bản (extends)Từ khóa superGhi đè phương thức (@Override)Chuỗi gọi hàm khởi tạoLớp ObjectKế thừa đơn và đa cấpVì sao không hỗ trợ kế thừa nhiều lớpÔn tập: Phân cấp lớp Employee7Phương thức đặc biệt và lớp Object
Phương thức toString()equals() và hashCode()Phương thức clone()compareTo() và ComparableGiao diện ComparatorÔn tập: sắp xếp tùy chỉnh2Bộ điều chỉnh quyền truy cập và đóng gói
Tổng quan về các mức truy cậpPhương thức getter và setterẨn thông tinTừ khóa finalÔn tập: Trình quản lý tài khoản ngân hàng5Tính đa hình
Kiến thức cơ bản về nạp chồng phương thứcGhi đè phương thức (thời gian chạy)Chuyển kiểu lên và chuyển kiểu xuốngToán tử instanceofLớp và phương thức trừu tượngÔn tập: máy tính hình học8Khái niệm OOP nâng cao
Kết hợp và kế thừaKết tập và hợp thànhLớp bên trong, lớp lồng nhau và lớp ẩn danhKiểu enum và phương thức enumBản ghi (Java 16+)Lớp sealed (Java 17+)11Mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu BuilderMẫu ObserverMẫu Strategy3Thuộc tính lớp và thành viên tĩnh
Biến của instance và biến tĩnhPhương thức tĩnhKhối tĩnhHằng số (static final)Ôn tập: bộ đếm và tiện ích6Giao diện và lớp trừu tượng
Giới thiệu về giao diệnTriển khai giao diệnTriển khai nhiều giao diệnPhương thức mặc định và tĩnh trong giao diệnLớp trừu tượng và giao diệnGiao diện hàmÔn tập: hệ thống thanh toán9Kiểu tổng quát
Giới thiệu về kiểu tổng quátLớp tổng quátPhương thức tổng quátTham số kiểu có giới hạnKý tự đại diện (?, extends, super)Ôn tập: vùng chứa tổng quát12Mẫu thiết kế, phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMẫu IteratorTự luyện tập: Trình biên dịch Java online