Design Pattern Series - Prototype (Golang)
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
- Prototype Interface: Định nghĩa một method để clone các đối tượng.
- Concrete Prototype: Triển khai method clone và lưu trữ trạng thái cần sao chép.
- 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
- Prototype Interface:
- Interface
Document
định nghĩa methodClone()
trả về mộtDocument
mới và methodDisplay()
để hiển thị trạng thái của đối tượng.
- Interface
- Concrete Prototype (Resume):
- Struct
Resume
triển khai interfaceDocument
. - 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.
- Struct
- 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.