Mẫu Composite
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 75 trên 87.
Mẫu Composite là một mẫu thiết kế cấu trúc cho phép bạn kết hợp các đối tượng thành cấu trúc cây để biểu diễn hệ phân cấp toàn thể và bộ phận. Mẫu này cho phép phía khách hàng xử lý đồng nhất các đối tượng riêng lẻ và các tổ hợp đối tượng thông qua một giao diện chung.
Hãy hình dung hệ thống tệp: một thư mục có thể chứa các tệp và thư mục khác, trong đó lại có thể chứa thêm nhiều tệp và thư mục. Dù bạn đang xử lý một tệp riêng lẻ hay cả cấu trúc thư mục, bạn đều muốn thực hiện các thao tác như «lấy kích thước» theo cùng một cách. Mẫu này định nghĩa một giao diện Component, các đối tượng Leaf (phần tử riêng lẻ) và các đối tượng Composite (vật chứa các đối tượng con):
interface FileComponent {
void display(String indent);
int getSize();
}
class File implements FileComponent {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public void display(String indent) {
System.out.println(indent + name + " (" + size + "KB)");
}
public int getSize() {
return size;
}
}
class Folder implements FileComponent {
private String name;
private List<FileComponent> children = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileComponent component) {
children.add(component);
}
public void display(String indent) {
System.out.println(indent + name + "/");
for (FileComponent child : children) {
child.display(indent + " ");
}
}
public int getSize() {
return children.stream().mapToInt(FileComponent::getSize).sum();
}
}Đối tượng tổng hợp ủy quyền các thao tác cho các đối tượng con, nhờ đó tạo nên cấu trúc đệ quy. Các đối tượng khách tương tác với cây mà không cần biết chúng đang làm việc với nút lá hay đối tượng tổng hợp:
Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));
Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);
root.display("");
System.out.println("Total: " + root.getSize() + "KB");Mẫu Composite phù hợp để biểu diễn các cấu trúc phân cấp như sơ đồ tổ chức, thành phần giao diện người dùng hoặc hệ thống menu, khi bạn cần xử lý các nhóm và từng mục riêng lẻ theo cùng một cách.
Thử thách
DễHãy xây dựng một hệ thống sơ đồ tổ chức bằng Composite Pattern! Bạn sẽ tạo một cấu trúc trong đó các phòng ban có thể chứa nhân viên và các phòng ban cấp dưới, cho phép bạn tính tổng lương và hiển thị hệ thống phân cấp theo cùng một cách, dù bạn đang xem một nhân viên riêng lẻ hay cả một bộ phận.
Bạn sẽ sắp xếp code trong bốn tệp:
OrganizationComponent.java: Định nghĩa giao diện thành phần mà cả nhân viên và phòng ban sẽ triển khai. Giao diện này cần khai báo hai phương thức:showDetails(String indent)để hiển thị thành phần với mức thụt lề phù hợp, vàgetSalary()để trả về tổng lương dưới dạng số nguyên.Employee.java: Tạo lớp lá đại diện cho từng nhân viên. MộtEmployeecó tên (String) và mức lương (int), cả hai được thiết lập thông qua hàm khởi tạo. Khi gọishowDetails, phương thức này cần in ra[indent][name]: $[salary]. Phương thứcgetSalarychỉ cần trả về mức lương của nhân viên.Department.java: Tạo lớp tổng hợp có thể chứa cả nhân viên và các phòng ban cấp dưới. MộtDepartmentcó tên (String) và duy trì một danh sách các phần tử con thuộc kiểuOrganizationComponent. Lớp này cần có phương thứcadd(OrganizationComponent component)để thêm thành viên. Khi gọishowDetails, phương thức này in ra[indent][name] Department, sau đó gọishowDetailstrên từng phần tử con với mức thụt lề tăng thêm hai dấu cách. Phương thứcgetSalarytrả về tổng lương của tất cả phần tử con.Main.java: Xây dựng tổ chức của bạn! Bạn sẽ nhận bốn đầu vào: tên và mức lương của hai nhân viên, cùng với tên của một phòng ban cấp dưới và một nhân viên thuộc phòng ban đó.Tạo phòng ban gốc có tên "Engineering". Thêm vào đó hai nhân viên bằng hai cặp tên và mức lương đầu tiên. Sau đó, tạo một phòng ban cấp dưới bằng đầu vào thứ ba, thêm một nhân viên vào đó bằng cặp tên và mức lương thứ tư, rồi thêm phòng ban cấp dưới này vào Engineering.
Gọi
showDetails("")trên phòng ban Engineering, sau đó in tổng lương theo định dạngTotal Salary: $[amount].
Bạn sẽ nhận đầu vào theo thứ tự này: tên employee1 (String), mức lương employee1 (int), tên employee2 (String), mức lương employee2 (int), tên phòng ban cấp dưới (String), tên employee3 (String), mức lương employee3 (int).
Ví dụ, với các đầu vào Alice, 75000, Bob, 65000, QA, Charlie, 55000, đầu ra của bạn sẽ là:
Engineering Department
Alice: $75000
Bob: $65000
QA Department
Charlie: $55000
Total Salary: $195000Hãy chú ý cách cùng các phương thức showDetails và getSalary hoạt động liền mạch dù được gọi trên một nhân viên riêng lẻ hay cả phòng ban chứa các phòng ban cấp dưới lồng nhau. Cấu trúc tổng hợp tự động xử lý đệ quy, giúp bạn xử lý toàn bộ hệ thống phân cấp theo cùng một cách!
Tự làm thử
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Đọc dữ liệu đầu vào
String emp1Name = scanner.nextLine();
int emp1Salary = Integer.parseInt(scanner.nextLine());
String emp2Name = scanner.nextLine();
int emp2Salary = Integer.parseInt(scanner.nextLine());
String subDeptName = scanner.nextLine();
String emp3Name = scanner.nextLine();
int emp3Salary = Integer.parseInt(scanner.nextLine());
// TODO: Tạo phòng ban "Engineering" làm gốc
// TODO: Tạo hai nhân viên từ dữ liệu emp1 và emp2 rồi thêm vào Engineering
// TODO: Tạo phòng ban con bằng subDeptName
// TODO: Tạo một nhân viên từ dữ liệu emp3 rồi thêm vào phòng ban con
// TODO: Thêm phòng ban con vào Engineering
// TODO: Gọi showDetails("") trên phòng ban Engineering
// TODO: In tổng lương theo định dạng: Total Salary: $[amount]
}
}
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+)3Thuộ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