Hướng dẫn Java Design Pattern – Composite

Trong các bài viết trước chúng ta đã tìm hiểu về Bridge Pattern và Adapter Pattern. Trong bài viết này chúng ta tiếp tục tìm hiểu một Design Pattern khác thuộc nhóm cấu trúc là Composite Pattern.

1. Composite Pattern là gì?

Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

Composite là một mẫu thiết kế thuộc nhóm cấu trúc (Structural Pattern). Composite Pattern là một sự tổng hợp những thành phần có quan hệ với nhau để tạo ra thành phần lớn hơn. Nó cho phép thực hiện các tương tác với tất cả đối tượng trong mẫu tương tự nhau.

Composite Pattern được sử dụng khi chúng ta cần xử lý một nhóm đối tượng tương tự theo cách xử lý 1 object. Composite pattern sắp xếp các object theo cấu trúc cây để diễn giải 1 phần cũng như toàn bộ hệ thống phân cấp. Pattern này tạo một lớp chứa nhóm đối tượng của riêng nó. Lớp này cung cấp các cách để sửa đổi nhóm của cùng 1 object. Pattern này cho phép Client có thể viết code giống nhau để tương tác với composite object này, bất kể đó là một đối tượng riêng lẻ hay tập hợp các đối tượng.

Ví dụ: Một chương trình quản lý một hệ thống tập tin với cấu trúc như hình sau:

Một hệ thống tập tin là một cấu trúc cây có chứa các nhánh là các thư mục (folder – composite), cũng như các nút lá là các tệp (file – leaf). Một folder có thể chứa một hoặc nhiều file hoặc folder. Do đó, folder là một đối tượng phức tạp và file là một đối tượng đơn giản. File và Folder có nhiều thao tác và thuộc tính chung, chẳng hạn như: di chuyển (cut) , sao chép (copy), liệt kê (view) hoặc các thuộc tính thư mục như tên tệp và kích thước.

Với cấu trúc như vậy sẽ dễ dàng và thuận tiện hơn để quản lý file và folder thống nhất bằng cách xây dựng một Interface có đầy đủ các phương thức và thuộc tính chung cho cả file và folder.

2. Cài đặt Composite Pattern như thế nào?

Một Composite Pattern bao gồm các thành phần cơ bản sau:

  • Base Component : là một interface hoặc abstract class quy định các method chung cần phải có cho tất cả các thành phần tham gia vào mẫu này.
  • Leaf : là lớp hiện thực (implements) các phương thức của Component. Nó là các object không có con.
  • Composite : lưu trữ tập hợp các Leaf và cài đặt các phương thức của Base Component. Composite cài đặt các phương thức được định nghĩa trong interface Component bằng cách ủy nhiệm cho các thành phần con xử lý.
  • Client: sử dụng Base Component để làm việc với các đối tượng trong Composite.

Ví dụ: Cài đặt Composite Pattern về chương trình quản lý một hệ thống tập tin ở trên.

  • Trước hết chúng ta định nghĩa một Inteface Component (FileComponent) có các phương thức chung cho cả folder và file. Để đơn giản, tôi chỉ tạo 2 phương thức showProperty() và totalSize(). Hai phương thức này sẽ cung cấp thông tin về file và tổng kích thước của nó.
  • Tiếp theo, chúng ta sẽ tạo một class Leaf cài đặt các phương thức của Component (FileLeaf). Một class Composite chứa tập hợp các Leaf và cài đặt các phương thức của Component (FolderComposite).
  • Cuối cùng, chúng ta sẽ tạo một class Client gọi các phương thức của FileComponent và FolderComposite. Cách gọi các phương thức của 2 class này hoàn toàn giống nhau do cùng implement một Component (FileComponent).

FileComponent.java

package com.maixuanviet.patterns.structural.composite;
 
public interface FileComponent {
    void showProperty();
    long totalSize();
}

FileLeaf.java

package com.maixuanviet.patterns.structural.composite;
 
public class FileLeaf implements FileComponent {
 
    private String name;
    private long size;
 
    public FileLeaf(String name, long size) {
        super();
        this.name = name;
        this.size = size;
    }
 
    @Override
    public long totalSize() {
        return size;
    }
 
    @Override
    public void showProperty() {
        System.out.println("FileLeaf [name=" + name + ", size=" + size + "]");
    }
}

FolderComposite.java

package com.maixuanviet.patterns.structural.composite;
 
import java.util.ArrayList;
import java.util.List;
 
public class FolderComposite implements FileComponent {
 
    private List<FileComponent> files = new ArrayList<>();
 
    public FolderComposite(List<FileComponent> files) {
        this.files = files;
    }
 
    @Override
    public void showProperty() {
        for (FileComponent file : files) {
            file.showProperty();
        }
    }
 
    @Override
    public long totalSize() {
        long total = 0;
        for (FileComponent file : files) {
            total += file.totalSize();
        }
        return total;
    }
}

Client.java

package com.maixuanviet.patterns.structural.composite;
 
import java.util.Arrays;
import java.util.List;
 
public class Client {
 
    public static void main(String[] args) {
        FileComponent file1 = new FileLeaf("file 1", 10);
        FileComponent file2 = new FileLeaf("file 2", 5);
        FileComponent file3 = new FileLeaf("file 3", 12);
 
        List<FileComponent> files = Arrays.asList(file1, file2, file3);
        FileComponent folder = new FolderComposite(files);
        folder.showProperty();
        System.out.println("Total Size: " + folder.totalSize());
    }
}

Output của chương trình trên:

FileLeaf [name=file 1, size=10]
FileLeaf [name=file 2, size=5]
FileLeaf [name=file 3, size=12]
Total Size: 27

3. Lợi ích của Composite Pattern là gì?

  • Cung cấp cùng một cách sử dụng đối với từng đối tượng riêng lẻ hoặc nhóm các đối tượng với nhau.

4. Sử dụng Composite Pattern khi nào?

  • Composite Pattern chỉ nên được áp dụng khi nhóm đối tượng phải hoạt động như một đối tượng duy nhất (theo cùng một cách).
  • Composite Pattern có thể được sử dụng để tạo ra một cấu trúc giống như cấu trúc cây.