Menu
CoddyTech

Giới thiệu về mẫu thiết kế

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 85 trên 107.

Mẫu thiết kế là những giải pháp có thể tái sử dụng cho các vấn đề thường gặp trong thiết kế phần mềm. Chúng không phải là code để bạn sao chép trực tiếp, mà là những khuôn mẫu hướng dẫn cách bạn cấu trúc các kiểu dữ liệu và cách chúng tương tác để giải quyết những thử thách cụ thể.

Các mẫu thiết kế thường được chia thành ba nhóm:

NhómMục đíchVí dụ
Khởi tạoCơ chế tạo đối tượngSingleton, Factory, Builder
Cấu trúcKết hợp các kiểu thành cấu trúc lớn hơnAdapter, Decorator, Composite
Hành viGiao tiếp giữa các đối tượngObserver, Strategy, Command

Trong Go, các mẫu thiết kế trông khác với các ngôn ngữ OOP truyền thống.

Không dùng lớp (class) hay kế thừa (inheritance), Go triển khai các mẫu bằng giao diện (interface), nhúng struct và phép hợp thành. Ví dụ, Java có thể dùng lớp trừu tượng, còn Go dùng giao diện. C++ có thể dùng kế thừa, còn Go dùng cách nhúng.

Học các mẫu thiết kế giúp bạn nhận ra những vấn đề thường gặp và trao đổi giải pháp hiệu quả với các nhà phát triển khác. Khi ai đó nói "hãy dùng mẫu Strategy ở đây", cả nhóm sẽ hiểu cách tiếp cận mà không cần giải thích dài dòng.

Trong các bài học tiếp theo, bạn sẽ triển khai một số mẫu thiết kế cơ bản trong Go, bắt đầu với các mẫu khởi tạo như Singleton và Factory, sau đó chuyển sang các mẫu hành vi như Observer và Strategy.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống thông báo đơn giản để tìm hiểu cách các mẫu thiết kế giúp tổ chức code! Bạn sẽ tạo một cấu trúc có thể dễ dàng mở rộng bằng các mẫu như Observer hoặc Strategy trong những bài học sau. Còn bây giờ, hãy tập trung thiết lập các giao diện và kiểu dữ liệu rõ ràng, tuân theo những nguyên tắc lập trình hướng đối tượng tốt.

Bạn sẽ tổ chức code trong ba tệp:

  • notifier.go: Định nghĩa giao diện cốt lõi và các kiểu dữ liệu cho hệ thống thông báo.

    Tạo một giao diện Notifier với một phương thức duy nhất là Send(message string) string, trả về một thông báo xác nhận.

    Triển khai hai kiểu thông báo cụ thể:

    • EmailNotifier có trường Address. Phương thức Send của kiểu này trả về Email to [address]: [message]
    • SMSNotifier có trường PhoneNumber. Phương thức Send của kiểu này trả về SMS to [phone]: [message]
  • dispatcher.go: Tạo một bộ điều phối có thể làm việc với mọi kiểu thông báo thông qua giao diện.

    Xây dựng một struct NotificationDispatcher chứa một lát cắt các giao diện Notifier. Thêm các phương thức sau:

    • AddNotifier(n Notifier) để đăng ký một kiểu thông báo
    • Broadcast(message string) []string để gửi thông báo qua tất cả các kiểu thông báo đã đăng ký và trả về một lát cắt chứa tất cả các chuỗi xác nhận

    Đồng thời tạo hàm khởi tạo NewNotificationDispatcher() để trả về một bộ điều phối đã được khởi tạo.

  • main.go: Thiết lập các kiểu thông báo và phát thông báo.

    Đọc số lượng kiểu thông báo cần tạo. Với mỗi kiểu thông báo, đọc loại (email hoặc sms) và địa chỉ nhận (địa chỉ email hoặc số điện thoại). Thêm từng kiểu vào bộ điều phối.

    Sau đó, đọc nội dung thông báo cần phát. Gửi thông báo qua tất cả các kiểu thông báo và in từng xác nhận trên một dòng riêng.

Đầu vào sẽ có dạng sau:

  • Dòng 1: Số lượng kiểu thông báo
  • Với mỗi kiểu thông báo: loại (email hoặc sms), sau đó là địa chỉ nhận
  • Dòng cuối: Nội dung thông báo cần phát

Ví dụ, với đầu vào:

3
email
alice@example.com
sms
555-1234
email
bob@example.com
Server maintenance at midnight

Đầu ra sẽ là:

Email to alice@example.com: Server maintenance at midnight
SMS to 555-1234: Server maintenance at midnight
Email to bob@example.com: Server maintenance at midnight

Với đầu vào:

2
sms
555-9999
sms
555-0000
Meeting canceled

Đầu ra sẽ là:

SMS to 555-9999: Meeting canceled
SMS to 555-0000: Meeting canceled

Cấu trúc này cho thấy giao diện giúp thiết kế linh hoạt và dễ mở rộng như thế nào. Bộ điều phối không cần biết các kiểu thông báo cụ thể. Nó chỉ cần biết chúng có thể Send. Đây là nền tảng để xây dựng các mẫu như Observer và Strategy!

Tự làm thử

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Đọc số lượng bộ thông báo
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	// Tạo bộ điều phối mới
	dispatcher := NewNotificationDispatcher()

	// Đọc từng bộ thông báo và thêm vào bộ điều phối
	for i := 0; i < count; i++ {
		notifierType, _ := reader.ReadString('\n')
		notifierType = strings.TrimSpace(notifierType)

		destination, _ := reader.ReadString('\n')
		destination = strings.TrimSpace(destination)

		// TODO: Tạo bộ thông báo phù hợp dựa trên loại
		// và thêm vào bộ điều phối
		// Gợi ý: Kiểm tra xem notifierType là "email" hay "sms"

	}

	// Đọc thông điệp cần gửi đến tất cả
	message, _ := reader.ReadString('\n')
	message = strings.TrimSpace(message)

	// TODO: Gửi thông điệp đến tất cả và in ra từng xác nhận

}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online