Design Pattern Series - Prototype (Golang)

Golang 9 Th09 2025

Bài viết trước trong series Builder Pattern

Prototype Design Pattern là một mẫu thiết kế thuộc nhóm creational, cho phép tạo các đối tượng mới bằng cách sao chép một đối tượng hiện có, được gọi là prototype, thay vì khởi tạo từ đầu. Mẫu này đặc biệt hữu ích khi việc tạo đối tượng tốn kém hoặc phức tạp, và bạn muốn tái sử dụng cấu trúc của một đối tượng hiện có.

Prototype Design Pattern là gì?

Prototype Pattern liên quan đến việc tạo một prototype object và sao chép nó để tạo ra các đối tượng mới. Thay vì sử dụng constructor hoặc factory, mẫu này dựa vào cơ chế cloning để tái tạo trạng thái của prototype. Sẽ hữu ích trong trường hợp:

  • Việc tạo đối tượng mới tốn nhiều tài nguyên (ví dụ: cần truy vấn database hoặc khởi tạo phức tạp).
  • Cần tạo các đối tượng với sự khác biệt nhỏ so với đối tượng gốc.
  • Muốn tránh sử dụng subclass để tạo đối tượng.

Trong Golang, vốn không có tính kế thừa truyền thống, Prototype Pattern có thể được triển khai bằng cách sử dụng struct và interface để đạt được chức năng tương tự.

Các Thành Phần Chính của Prototype Pattern

  1. Prototype Interface: Định nghĩa một method để clone các đối tượng.
  2. Concrete Prototype: Triển khai method clone và lưu trữ trạng thái cần sao chép.
  3. Client: Sử dụng prototype để tạo các đối tượng mới bằng cách clone.

Triển Khai Prototype Pattern trong Golang

Chúng ta sẽ tạo một hệ thống quản lý các loại tài liệu (ví dụ: resume) có thể được clone và tùy chỉnh để làm ví dụ cho pattern này:

=> Triển khai Prototype Pattern với một tài liệu resume.

package main

import (
	"fmt"
)

// Prototype interface
type Document interface {
	Clone() Document
	Display()
}

// Concrete Prototype: Resume
type Resume struct {
	Name      string
	Skills    []string
	Experience string
}

// Clone method for Resume
func (r *Resume) Clone() Document {
	// Deep copy to avoid slice share
	skillsCopy := make([]string, len(r.Skills))
	copy(skillsCopy, r.Skills)

	return &Resume{
		Name:      r.Name,
		Skills:    skillsCopy,
		Experience: r.Experience,
	}
}

// Display method for Resume
func (r *Resume) Display() {
	fmt.Printf("Resume: %s\nSkills: %v\nExperience: %s\n\n", r.Name, r.Skills, r.Experience)
}

func main() {
	// Create a prototype resume
	prototypeResume := &Resume{
		Name:      "John Doe",
		Skills:    []string{"Go", "Python", "SQL"},
		Experience: "5 years of SE",
	}

	// Clone prototype to create new resume
	resume1 := prototypeResume.Clone()
	resume2 := prototypeResume.Clone()

	// Edit created resume
	resume1.(*Resume).Name = "Alice Smith"
	resume1.(*Resume).Skills = append(resume1.(*Resume).Skills, "JavaScript")

	resume2.(*Resume).Name = "Bob Johnson"
	resume2.(*Resume).Experience = "3 years of Backend Developer"

	// Render
	fmt.Println("Prototype Resume:")
	prototypeResume.Display()

	fmt.Println("Cloned Resume 1:")
	resume1.Display()

	fmt.Println("Cloned Resume 2:")
	resume2.Display()
}

Giải Thích Code

  1. Prototype Interface:
    • Interface Document định nghĩa method Clone() trả về một Document mới và method Display() để hiển thị trạng thái của đối tượng.
  2. Concrete Prototype (Resume):
    • Struct Resume triển khai interface Document.
    • Method Clone() tạo một bản sao (deep copy) của resume, đảm bảo rằng các slice như Skills không bị chia sẻ giữa prototype và bản sao.
    • Method Display() in chi tiết của resume.
  3. Client (main):
    • Một prototype resume được tạo với các giá trị mặc định.
    • Hai resume mới được tạo bằng cách clone prototype.
    • Mỗi resume được clone sẽ được chỉnh sửa độc lập mà không ảnh hưởng đến prototype hoặc các bản sao khác.

Kết Quả Đầu Ra

Prototype Resume:
Resume: John Doe
Skills: [Go Python SQL]
Experience: 5 năm làm Kỹ sư phần mềm

Cloned Resume 1:
Resume: Alice Smith
Skills: [Go Python SQL JavaScript]
Experience: 5 năm làm Kỹ sư phần mềm

Cloned Resume 2:
Resume: Bob Johnson
Skills: [Go Python SQL]
Experience: 3 năm làm Backend Developer

Điều này cho thấy các bản sao là độc lập với prototype và với nhau.

Lợi Ích của Prototype Pattern

  • Hiệu quả: Giảm chi phí tạo các đối tượng phức tạp bằng cách tái sử dụng các đối tượng hiện có.
  • Linh hoạt: Cho phép tạo động các đối tượng với các cấu hình khác nhau.
  • Đơn giản hóa việc tạo đối tượng: Tránh sử dụng constructor hoặc factory phức tạp, đặc biệt trong các ngôn ngữ như Go không có kế thừa theo kiểu class.
  • Tách rời: Client không cần biết chi tiết về cách tạo đối tượng.

Khi Nào Nên Sử Dụng Prototype Pattern

  • Khi việc khởi tạo đối tượng tốn nhiều tài nguyên (ví dụ: tải dữ liệu từ database).
  • Khi cần tạo nhiều đối tượng với các thuộc tính tương tự nhưng có sự khác biệt nhỏ.
  • Khi muốn tránh sử dụng subclass để tạo các biến thể của đối tượng.
  • Khi hệ thống cần độc lập với cách các đối tượng được tạo.

Lưu Ý trong Golang

  • Deep Copy vs. Shallow Copy: Trong Go, cần đảm bảo deep copy cho các trường như slice hoặc map để tránh chia sẻ tham chiếu ngoài ý muốn. Ví dụ trên đã sao chép slice Skills một cách rõ ràng.
  • Interface: Interface của Go rất phù hợp để định nghĩa method Clone(), giúp mẫu này linh hoạt và dễ tái sử dụng.
  • Concurrency: Nếu sử dụng trong môi trường đồng thời, hãy đảm bảo prototype là thread-safe hoặc bất biến để tránh race condition.

Kết Luận

Prototype Design Pattern là một công cụ mạnh mẽ để tạo đối tượng một cách hiệu quả trong Golang. Bằng cách tận dụng interface và sao chép cẩn thận, bạn có thể triển khai mẫu này để đơn giản hóa việc tạo đối tượng và cải thiện hiệu năng. Ví dụ trên minh họa cách clone một resume, nhưng mẫu này có thể được áp dụng cho nhiều lĩnh vực khác nhau, chẳng hạn như quản lý cấu hình, phát triển game, hoặc bất kỳ tình huống nào cần sao chép đối tượng.

Chuyên mục