Design Pattern Series - Prototype (Golang)

Golang Sep 9, 2025

Previous post in series Builder Pattern

Prototype Pattern là một mẫu thiết kế thuộc nhóm Creational Design Patterns, cho phép tạo ra các đối tượng mới bằng cách sao chép (clone) một đối tượng đã tồn tại (gọi là prototype) thay vì khởi tạo từ đầu. Mẫu thiết kế này đặc biệt hữu ích khi việc tạo mới một đối tượng tốn nhiều tài nguyên 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 có sẵn.

Prototype Design Pattern là gì?

Thay vì sử dụng constructor (hàm khởi tạo) hoặc factory, Prototype Pattern dựa vào cơ chế nhân bản (cloning) để tái tạo trạng thái của đối tượng mẫu. Nó được sử dụng trong các trường hợp:

  • Việc tạo đối tượng mới tiêu tốn nhiều tài nguyên (ví dụ: truy vấn database phức tạp hoặc khởi tạo nặng).
  • Cần tạo ra các đối tượng có sự khác biệt nhỏ so với đối tượng gốc.
  • Muốn tránh việc tạo quá nhiều lớp con (subclasses) chỉ để khởi tạo các biến thể của đối tượng.

Trong Golang, mặc dù không có cơ chế kế thừa truyền thống, chúng ta có thể triển khai Prototype Pattern cực kỳ hiệu quả thông qua StructInterface.

Thành phần chính

  1. Prototype Interface: Định nghĩa phương thức để sao chép chính nó.
  2. Concrete Prototype: Triển khai phương thức clone và lưu trữ trạng thái cần sao chép.
  3. Client: Sử dụng prototype để tạo ra đối tượng mới thông qua việc gọi hàm clone.

Triển khai Prototype Pattern trong Golang

Chúng ta sẽ xây dựng một hệ thống quản lý tài liệu (ví dụ: Sơ yếu lý lịch - Resume) có thể được nhân bản và tùy chỉnh để minh họa mẫu thiết kế này.

Bước 1: Khai báo Interface và Struct

package main

import (
	"fmt"
)

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

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

Bước 2: Triển khai phương thức Clone (Deep Copy)

// Phương thức Clone cho Resume
func (r *Resume) Clone() Document {
	// Thực hiện Deep Copy để tránh việc chia sẻ vùng nhớ slice
	skillsCopy := make([]string, len(r.Skills))
	copy(skillsCopy, r.Skills)

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

// Phương thức hiển thị thông tin
func (r *Resume) Display() {
	fmt.Printf("Resume: %s\nSkills: %v\nExperience: %s\n\n", r.Name, r.Skills, r.Experience)
}

Bước 3: Sử dụng tại Client

func main() {
	// Tạo một bản mẫu (prototype) ban đầu
	prototypeResume := &Resume{
		Name:       "John Doe",
		Skills:     []string{"Go", "Python", "SQL"},
		Experience: "5 years of SE",
	}

	// Nhân bản từ prototype để tạo các bản resume mới
	resume1 := prototypeResume.Clone()
	resume2 := prototypeResume.Clone()

	// Tùy chỉnh các bản sao độc lập
	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"

	// Hiển thị kết quả
	fmt.Println("--- Prototype Resume ---")
	prototypeResume.Display()

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

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

Kết quả (Output):

Plaintext

--- Prototype Resume ---
Resume: John Doe
Skills: [Go Python SQL]
Experience: 5 years of SE

--- Cloned Resume 1 ---
Resume: Alice Smith
Skills: [Go Python SQL JavaScript]
Experience: 5 years of SE

--- Cloned Resume 2 ---
Resume: Bob Johnson
Skills: [Go Python SQL]
Experience: 3 years of Backend Developer

Giải thích chi tiết

  • Deep Copy vs. Shallow Copy: Trong Go, khi struct có chứa các trường như slice hoặc map, nếu bạn chỉ gán trực tiếp, bản sao sẽ dùng chung vùng nhớ với bản gốc. Trong ví dụ trên, chúng ta đã sử dụng copy() để tạo một bản sao hoàn toàn mới cho slice Skills, đảm bảo tính độc lập.
  • Interface: Việc sử dụng interface Document giúp code linh hoạt hơn, client không cần quan tâm struct cụ thể bên dưới là gì, chỉ cần biết nó có khả năng Clone().

Lợi ích của Prototype Pattern

  1. Hiệu suất: Giảm chi phí khởi tạo cho các đối tượng phức tạp.
  2. Linh hoạt: Cho phép tạo ra các biến thể đối tượng một cách động.
  3. Đơn giản hóa: Tránh việc phải duy trì các hệ thống Factory phức tạp hoặc các constructor quá dài.
  4. Tính đóng gói: Client không cần biết chi tiết logic khởi tạo bên trong.

Khi nào nên áp dụng?

  • Khi việc khởi tạo đối tượng tốn kém (ví dụ: cần đọc dữ liệu từ file hoặc network).
  • Khi bạn cần nhiều đối tượng tương tự nhau nhưng chỉ khác biệt ở vài thuộc tính.
  • Khi muốn tách biệt logic tạo đối tượng khỏi hệ thống chính.

Tổng kết

Prototype Design Pattern là một công cụ mạnh mẽ trong Golang để quản lý việc tạo đối tượng một cách hiệu quả. Bằng cách tận dụng interface và kỹ thuật Deep Copy, bạn có thể đơn giản hóa mã nguồn và cải thiện hiệu năng ứng dụng đáng kể.

Cảm ơn bạn đã theo dõi bài viết!

Tags