1. Composite Pattern là gì?

Mẫu Composite là một mẫu thiết kế cấu trúc đối tượng được sử dụng phổ biến. Nó kết hợp các đối tượng thành một cấu trúc cây để biểu diễn mối quan hệ "phần - toàn" theo hình thức phân cấp, cho phép người dùng xử lý các đối tượng cá nhân và sự kết hợp của các đối tượng một cách nhất quán.

2. Đặc điểm và ưu điểm của mẫu Composite

Các ưu điểm chính của mẫu Composite là:

  1. Nó có thể định nghĩa rõ ràng các đối tượng phức tạp theo hình thái phân cấp, biểu diễn toàn bộ hoặc một phần cấu trúc đối tượng, dễ dàng thêm mới các thành phần.
  2. Nó cung cấp một giao diện thống nhất, làm cho việc truy cập đến các phần cấu trúc và đối tượng cá nhân đồng nhất, cho phép người dùng sử dụng cả đối tượng đơn và đối tượng kết hợp một cách đồng đều.

3. Các kịch bản áp dụng của mẫu Composite

  1. Khi bạn muốn biểu diễn mối quan hệ phần - toàn trong các đối tượng.
  2. Khi bạn muốn người dùng bỏ qua sự khác biệt giữa các đối tượng kết hợp và đối tượng cá nhân, và sử dụng đồng đều tất cả các đối tượng trong cấu trúc kết hợp.

4. Triển khai mẫu Composite trong Golang

Giả sử chúng ta đang thiết kế một ứng dụng thương mại điện tử, thư mục sản phẩm là một ví dụ tốt cho mẫu Composite. Một danh mục có thể chứa các danh mục khác, và cũng các sản phẩm (ví dụ: danh mục điện tử chứa điện thoại, máy tính, và điện thoại chứa iPhone, Samsung).

4.1 Sơ đồ lớp UML

Mẫu Composite trong Golang

4.2 Giới thiệu Ví dụ

Trong ví dụ này, chúng ta có Component (rõ ràng sử dụng tính năng giao diện của hướng đối tượng) là lớp cơ sở trừu tượng, và cả CompositeLeaf đều triển khai giao diện này, đại diện cho các đối tượng chứa và đối tượng cơ bản, tương ứng.

4.3 Bước Triển khai 1: Định nghĩa lớp thành phần trừu tượng

4.3.1 Định nghĩa giao diện của lớp thành phần trừu tượng

// Component: Giao diện thành phần cơ bản, định nghĩa sự tương đồng giữa nhóm và cá nhân
type Component interface {
    Search(string)
}

4.3.2 Triển khai các phương thức cơ bản của lớp thành phần trừu tượng

Bước này được triển khai cụ thể trong lớp thành phần chứa và lớp thành phần lá.

4.4 Bước Triển khai 2: Định nghĩa lớp thành phần lá

Lớp cụ thể này đại diện cho danh mục hoặc đối tượng dưới cùng trong cấu trúc phân cấp, và không có các đối tượng lớp kế tiếp.

4.4.1 Kế thừa từ lớp thành phần trừu tượng

Trong Go, việc kế thừa giao diện được triển khai thông qua cách triển khai phương thức thông qua Struct.

4.4.2 Triển khai các phương thức cụ thể của lớp thành phần lá

// Product: Đại diện cho một nút lá, tức là một sản phẩm, và không thể chứa nút con
type Product struct {
    Name string
}

// Search: Tìm kiếm sản phẩm
func (p *Product) Search(keyword string) {
    if strings.Contains(p.Name, keyword) {
        fmt.Printf("Product: '%s' chứa từ khóa: '%s'\n", p.Name, keyword)
    }
}

4.5 Bước Triển khai 3: Định nghĩa lớp thành phần chứa

Lớp này được sử dụng để lưu trữ và quản lý các đối tượng con, thông thường bao gồm một số phương thức để quản lý và tổ chức các đối tượng con, chẳng hạn như add(Component), remove(Component), v.v.

4.5.1 Kế thừa từ lớp thành phần trừu tượng

Điều này cũng được triển khai bằng cách sử dụng Struct để triển khai phương thức giao diện trong Go.

4.5.2 Triển khai các phương thức cụ thể của lớp thành phần chứa

// Category: Đại diện cho một nút chứa, tức là danh mục sản phẩm, có thể chứa các nút con
type Category struct {
    Name     string
    Children []Component
}

// Add: Thêm một nút con
func (c *Category) Add(child Component) {
    c.Children = append(c.Children, child)
}

// Remove: Xóa một nút con
func (c *Category) Remove(child Component) {
    // Triển khai cụ thể đã được bỏ qua
}

// Tìm kiếm: Tìm kiếm sản phẩm
func (c *Category) Search(keyword string) {
    fmt.Printf("Danh mục: %s\n", c.Name)
    for _, composite := range c.Children {
        composite.Search(keyword)
    }
}

4.6 Bước 4 triển khai: Ví dụ mã nguồn của Client

Khởi tạo một cấu trúc, gắn nó vào một cấu trúc cây, sau đó gọi thao tác truy cập vào cấu trúc cây.

func main() {
    root := &Category{Name: "Gốc"}
    điện_tử := &Category{Name: "Điện tử"}

    điện_thoại := &Product{Name: "Điện thoại"}
    tv := &Product{Name: "Tivi"}

    root.Add(điện_tử)
    điện_tử.Add(điện_thoại)
    điện_tử.Add(tv)

    root.Search("điện thoại") // Điều này sẽ tìm kiếm trong tất cả các con
}