🎨 Phong Cách Nấu - Design Patterns
Chào mừng đến bài học về Design Patterns - những phong cách nấu ăn đã được kiểm chứng trong Bếp Á!
🎯 Món Ăn Hôm Nay
Trong bếp chuyên nghiệp, mỗi đầu bếp đều có những "phong cách nấu" riêng - những cách tổ chức bếp núc, chuẩn bị nguyên liệu, và phục vụ món ăn đã được chứng minh hiệu quả qua thời gian. Trong lập trình, chúng ta gọi đó là Design Patterns - những giải pháp thiết kế đã được kiểm chứng cho các vấn đề phổ biến!
🥘 Design Pattern Là Gì?
Design Pattern = Phong cách nấu ăn của các đầu bếp chuyên nghiệp
Giống như nhà hàng có nhiều phong cách tổ chức khác nhau:
- Chỉ một đầu bếp trưởng → Singleton Pattern
- Nhà máy sản xuất món ăn → Factory Pattern
- Theo dõi đơn hàng → Observer Pattern
- Trang trí món ăn → Decorator Pattern
💡 Lợi Ích: Không cần "phát minh lại bánh xe" - học từ kinh nghiệm của những đầu bếp giỏi!
👨🍳 Câu Chuyện Trong Bếp
Bạn vừa được thăng chức làm đầu bếp trưởng của một nhà hàng lớn. Bạn phát hiện:
- Có 5 người cùng quản lý kho → hỗn loạn! → Cần Singleton
- Mỗi món phở đều nấu thủ công từ đầu → chậm! → Cần Factory
- Khách đặt món nhưng bếp không biết → mất đơn! → Cần Observer
- Mỗi món cần trang trí khác nhau → code lặp lại! → Cần Decorator
Hãy cùng học các "phong cách nấu" giải quyết những vấn đề này!
📝 1. Singleton Pattern - Chỉ Một Đầu Bếp Trưởng
Vấn Đề
Nhà hàng chỉ nên có một đầu bếp trưởng quản lý cấu hình toàn bộ bếp. Nếu có nhiều người cùng quản lý → xung đột, hỗn loạn!
Giải Pháp - Singleton
package main
import (
    "fmt"
    "sync"
)
// Config nhà hàng - chỉ có DUY NHẤT một instance
type RestaurantConfig struct {
    TenNhaHang    string
    DiaChi        string
    SoBan         int
    GioMoCua      string
}
var (
    instance *RestaurantConfig
    once     sync.Once
)
// GetConfig - Lấy thông tin cấu hình duy nhất
func GetConfig() *RestaurantConfig {
    once.Do(func() {
        fmt.Println("🏪 Bổ nhiệm đầu bếp trưởng lần đầu tiên!")
        instance = &RestaurantConfig{
            TenNhaHang: "Bếp Á Hà Nội",
            DiaChi:     "123 Phố Huế",
            SoBan:      30,
            GioMoCua:   "08:00 - 22:00",
        }
    })
    return instance
}
func main() {
    // Lần 1: Tạo đầu bếp trưởng
    config1 := GetConfig()
    fmt.Printf("Nhà hàng: %s\n", config1.TenNhaHang)
    // Lần 2: Vẫn là đầu bếp trưởng cũ (không tạo mới)
    config2 := GetConfig()
    // Kiểm tra: cùng một người
    fmt.Printf("Cùng một đầu bếp? %v\n", config1 == config2) // true
}
🔍 Giải Thích:
sync.Onceđảm bảo chỉ khởi tạo một lần duy nhất- Dù gọi
GetConfig()bao nhiêu lần cũng chỉ có 1 instance- Giống như nhà hàng chỉ có 1 đầu bếp trưởng quản lý toàn bộ
Khi Nào Dùng?
- ✅ Quản lý cấu hình toàn cục
- ✅ Kết nối database duy nhất
- ✅ Logger chung cho toàn ứng dụng
📝 2. Factory Pattern - Nhà Máy Sản Xuất Món Ăn
Vấn Đề
Khách gọi nhiều loại món khác nhau (phở, bún, gỏi...). Nếu mỗi món đều tự nấu từ đầu → code dài, khó bảo trì!
Giải Pháp - Factory
package main
import "fmt"
// Interface chung cho mọi món ăn
type MonAn interface {
    CheBien() string
    PhucVu()
}
// Món Phở
type Pho struct {
    Loai string
}
func (p *Pho) CheBien() string {
    return "Nấu nước dùng 6 tiếng + bánh phở"
}
func (p *Pho) PhucVu() {
    fmt.Printf("🍜 %s sẵn sàng! %s\n", p.Loai, p.CheBien())
}
// Món Bún
type Bun struct {
    Loai string
}
func (b *Bun) CheBien() string {
    return "Luộc bún + sốt đặc biệt"
}
func (b *Bun) PhucVu() {
    fmt.Printf("🍝 %s sẵn sàng! %s\n", b.Loai, b.CheBien())
}
// Món Gỏi
type Goi struct {
    Loai string
}
func (g *Goi) CheBien() string {
    return "Gói rau + nước chấm"
}
func (g *Goi) PhucVu() {
    fmt.Printf("🥗 %s sẵn sàng! %s\n", g.Loai, g.CheBien())
}
// FACTORY - Nhà máy sản xuất món ăn
func TaoMonAn(loaiMon string) MonAn {
    switch loaiMon {
    case "pho":
        return &Pho{Loai: "Phở Bò"}
    case "bun":
        return &Bun{Loai: "Bún Chả"}
    case "goi":
        return &Goi{Loai: "Gỏi Cuốn"}
    default:
        return nil
    }
}
func main() {
    // Khách gọi món → Factory tự động tạo
    donHang := []string{"pho", "bun", "goi"}
    for _, tenMon := range donHang {
        mon := TaoMonAn(tenMon)
        if mon != nil {
            mon.PhucVu()
        }
    }
}
🔍 Giải Thích:
TaoMonAn()là nhà máy - nhận tên món, trả ra món đã chuẩn bị- Không cần biết chi tiết cách nấu từng món
- Dễ mở rộng: thêm món mới chỉ cần sửa Factory
Khi Nào Dùng?
- ✅ Tạo nhiều loại object khác nhau
- ✅ Logic tạo object phức tạp
- ✅ Muốn tách biệt việc tạo và sử dụng object
📝 3. Observer Pattern - Theo Dõi Đơn Hàng
Vấn Đề
Khách đặt món → Bếp cần biết ngay để nấu → Thu ngân cần biết để in hóa đơn → Shipper cần biết để giao hàng. Làm sao thông báo cho tất cả?
Giải Pháp - Observer
package main
import "fmt"
// Observer - Người quan sát (Bếp, Thu ngân, Shipper...)
type Observer interface {
    NhanThongBao(donHang string)
}
// Subject - Hệ thống đặt món
type DatMonService struct {
    observers []Observer
}
// Đăng ký người quan sát
func (d *DatMonService) DangKy(o Observer) {
    d.observers = append(d.observers, o)
}
// Thông báo cho tất cả
func (d *DatMonService) ThongBaoTatCa(donHang string) {
    fmt.Printf("\n📢 ĐƠN HÀNG MỚI: %s\n", donHang)
    for _, observer := range d.observers {
        observer.NhanThongBao(donHang)
    }
}
// Bếp - Observer 1
type Bep struct{}
func (b *Bep) NhanThongBao(donHang string) {
    fmt.Printf("👨🍳 Bếp: Bắt đầu nấu '%s'\n", donHang)
}
// Thu Ngân - Observer 2
type ThuNgan struct{}
func (t *ThuNgan) NhanThongBao(donHang string) {
    fmt.Printf("💰 Thu ngân: In hóa đơn cho '%s'\n", donHang)
}
// Shipper - Observer 3
type Shipper struct{}
func (s *Shipper) NhanThongBao(donHang string) {
    fmt.Printf("🚗 Shipper: Chuẩn bị giao '%s'\n", donHang)
}
func main() {
    // Khởi tạo hệ thống đặt món
    heThong := &DatMonService{}
    // Đăng ký các bộ phận theo dõi
    heThong.DangKy(&Bep{})
    heThong.DangKy(&ThuNgan{})
    heThong.DangKy(&Shipper{})
    // Khách đặt món → Tất cả được thông báo tự động
    heThong.ThongBaoTatCa("2 Phở Bò + 1 Bún Chả")
    heThong.ThongBaoTatCa("3 Gỏi Cuốn")
}
🔍 Giải Thích:
- Subject (DatMonService): Hệ thống nhận đơn hàng
- Observer (Bếp, Thu ngân, Shipper): Bộ phận cần được thông báo
- Khi có đơn mới → tự động thông báo cho tất cả observers
Khi Nào Dùng?
- ✅ Event handling (xử lý sự kiện)
- ✅ Notification system (thông báo)
- ✅ Real-time updates (cập nhật real-time)
📝 4. Decorator Pattern - Trang Trí Món Ăn
Vấn Đề
Một món phở có thể có nhiều tùy chọn: thêm trứng, thêm tái, thêm gân... Nếu tạo class riêng cho mỗi kết hợp → quá nhiều class!
Giải Pháp - Decorator
package main
import "fmt"
// Interface món ăn cơ bản
type MonAn interface {
    MoTa() string
    Gia() int
}
// Phở cơ bản
type PhoCoBan struct{}
func (p *PhoCoBan) MoTa() string {
    return "Phở Bò"
}
func (p *PhoCoBan) Gia() int {
    return 40000
}
// Decorator - Thêm trứng
type ThemTrung struct {
    mon MonAn
}
func (t *ThemTrung) MoTa() string {
    return t.mon.MoTa() + " + Trứng"
}
func (t *ThemTrung) Gia() int {
    return t.mon.Gia() + 10000
}
// Decorator - Thêm tái
type ThemTai struct {
    mon MonAn
}
func (t *ThemTai) MoTa() string {
    return t.mon.MoTa() + " + Tái"
}
func (t *ThemTai) Gia() int {
    return t.mon.Gia() + 15000
}
// Decorator - Thêm gân
type ThemGan struct {
    mon MonAn
}
func (t *ThemGan) MoTa() string {
    return t.mon.MoTa() + " + Gân"
}
func (t *ThemGan) Gia() int {
    return t.mon.Gia() + 20000
}
func main() {
    // Món cơ bản
    pho := &PhoCoBan{}
    fmt.Printf("%s: %d VND\n", pho.MoTa(), pho.Gia())
    // Thêm trứng
    phoTrung := &ThemTrung{mon: pho}
    fmt.Printf("%s: %d VND\n", phoTrung.MoTa(), phoTrung.Gia())
    // Thêm trứng + tái
    phoTrungTai := &ThemTai{mon: phoTrung}
    fmt.Printf("%s: %d VND\n", phoTrungTai.MoTa(), phoTrungTai.Gia())
    // Thêm trứng + tái + gân (full option!)
    phoFullOption := &ThemGan{mon: phoTrungTai}
    fmt.Printf("%s: %d VND\n", phoFullOption.MoTa(), phoFullOption.Gia())
}
🔍 Giải Thích:
- Base Component (PhoCoBan): Món gốc
- Decorator (ThemTrung, ThemTai...): Các lớp "trang trí" thêm vào
- Có thể kết hợp linh hoạt: trứng, tái, gân...
- Không cần tạo class
PhoTrungTaiGan- chỉ cần "xếp chồng" các decorator!
Khi Nào Dùng?
- ✅ Thêm tính năng động cho object
- ✅ Tránh tạo quá nhiều subclass
- ✅ Kết hợp linh hoạt các tính năng
📝 5. Strategy Pattern - Chiến Lược Giảm Giá
Vấn Đề
Nhà hàng có nhiều chương trình giảm giá: sinh viên giảm 10%, thẻ VIP giảm 20%, giờ vàng giảm 15%... Làm sao tổ chức code gọn gàng?
Giải Pháp - Strategy
package main
import "fmt"
// Interface chiến lược giảm giá
type ChienLuocGiamGia interface {
    TinhGiam(giaGoc int) int
}
// Giảm giá sinh viên - 10%
type GiamGiaSinhVien struct{}
func (g *GiamGiaSinhVien) TinhGiam(giaGoc int) int {
    return giaGoc * 10 / 100
}
// Giảm giá VIP - 20%
type GiamGiaVIP struct{}
func (g *GiamGiaVIP) TinhGiam(giaGoc int) int {
    return giaGoc * 20 / 100
}
// Giảm giá giờ vàng - 15%
type GiamGiaGioVang struct{}
func (g *GiamGiaGioVang) TinhGiam(giaGoc int) int {
    return giaGoc * 15 / 100
}
// Hóa đơn - sử dụng strategy
type HoaDon struct {
    tongTien       int
    chienLuocGiam  ChienLuocGiamGia
}
func (h *HoaDon) SetChienLuoc(cl ChienLuocGiamGia) {
    h.chienLuocGiam = cl
}
func (h *HoaDon) TinhTongCuoi() int {
    if h.chienLuocGiam != nil {
        giam := h.chienLuocGiam.TinhGiam(h.tongTien)
        return h.tongTien - giam
    }
    return h.tongTien
}
func main() {
    hoaDon := &HoaDon{tongTien: 100000}
    // Khách là sinh viên
    hoaDon.SetChienLuoc(&GiamGiaSinhVien{})
    fmt.Printf("Sinh viên trả: %d VND\n", hoaDon.TinhTongCuoi())
    // Khách có thẻ VIP
    hoaDon.SetChienLuoc(&GiamGiaVIP{})
    fmt.Printf("VIP trả: %d VND\n", hoaDon.TinhTongCuoi())
    // Giờ vàng
    hoaDon.SetChienLuoc(&GiamGiaGioVang{})
    fmt.Printf("Giờ vàng trả: %d VND\n", hoaDon.TinhTongCuoi())
}
🔍 Giải Thích:
- Strategy Interface: Định nghĩa cách tính giảm giá
- Concrete Strategies: Các cách tính cụ thể (sinh viên, VIP, giờ vàng)
- Context (HoaDon): Sử dụng strategy để tính tiền
- Dễ thêm chiến lược mới mà không sửa code cũ!
Khi Nào Dùng?
- ✅ Nhiều thuật toán khác nhau cho cùng một việc
- ✅ Cần đổi thuật toán runtime
- ✅ Tránh nhiều if-else phức tạp
📝 6. Builder Pattern - Xây Dựng Đơn Hàng Phức Tạp
Vấn Đề
Đơn hàng có nhiều tùy chọn: loại món, size, topping, ghi chú đặc biệt... Constructor có quá nhiều tham số!
Giải Pháp - Builder
package main
import "fmt"
// Đơn hàng phức tạp
type DonHang struct {
    MonChinh    string
    Size        string
    Toppings    []string
    GhiChu      string
    GiaoTanNoi  bool
}
// Builder
type DonHangBuilder struct {
    donHang *DonHang
}
func NewDonHangBuilder() *DonHangBuilder {
    return &DonHangBuilder{
        donHang: &DonHang{
            Toppings: []string{},
        },
    }
}
func (b *DonHangBuilder) ChonMon(mon string) *DonHangBuilder {
    b.donHang.MonChinh = mon
    return b
}
func (b *DonHangBuilder) ChonSize(size string) *DonHangBuilder {
    b.donHang.Size = size
    return b
}
func (b *DonHangBuilder) ThemTopping(topping string) *DonHangBuilder {
    b.donHang.Toppings = append(b.donHang.Toppings, topping)
    return b
}
func (b *DonHangBuilder) ThemGhiChu(ghiChu string) *DonHangBuilder {
    b.donHang.GhiChu = ghiChu
    return b
}
func (b *DonHangBuilder) GiaoTanNoi(co bool) *DonHangBuilder {
    b.donHang.GiaoTanNoi = co
    return b
}
func (b *DonHangBuilder) Build() *DonHang {
    return b.donHang
}
func (d *DonHang) HienThi() {
    fmt.Println("\n📋 ĐƠN HÀNG:")
    fmt.Printf("Món: %s (%s)\n", d.MonChinh, d.Size)
    if len(d.Toppings) > 0 {
        fmt.Printf("Toppings: %v\n", d.Toppings)
    }
    if d.GhiChu != "" {
        fmt.Printf("Ghi chú: %s\n", d.GhiChu)
    }
    if d.GiaoTanNoi {
        fmt.Println("🚗 Giao tận nơi")
    }
}
func main() {
    // Xây dựng đơn hàng phức tạp - dễ đọc!
    don := NewDonHangBuilder().
        ChonMon("Phở Bò").
        ChonSize("Lớn").
        ThemTopping("Trứng").
        ThemTopping("Tái").
        ThemTopping("Gân").
        ThemGhiChu("Ít hành, nhiều rau").
        GiaoTanNoi(true).
        Build()
    don.HienThi()
    // Đơn giản hơn
    donGianDon := NewDonHangBuilder().
        ChonMon("Bún Chả").
        ChonSize("Vừa").
        Build()
    donGianDon.HienThi()
}
🔍 Giải Thích:
- Builder: Xây dựng object từng bước
- Fluent Interface: Method chaining (
.ChonMon().ChonSize()...)- Dễ đọc, dễ hiểu hơn constructor với 10 tham số!
- Linh hoạt: chỉ điền thông tin cần thiết
Khi Nào Dùng?
- ✅ Object có nhiều tham số khởi tạo
- ✅ Một số tham số optional
- ✅ Muốn code dễ đọc, dễ maintain
🔥 Thực Hành Trong Bếp
Bài Tập 1: Singleton Logger
Tạo một logger duy nhất cho toàn bộ nhà hàng:
type Logger struct {
    // ... thông tin logger
}
func GetLogger() *Logger {
    // TODO: Implement Singleton pattern
}
Bài Tập 2: Factory Đồ Uống
Tạo factory cho các loại đồ uống (trà, cà phê, nước ngọt):
type DoUong interface {
    Pha() string
}
func TaoDoUong(loai string) DoUong {
    // TODO: Implement Factory pattern
}
Bài Tập 3: Observer Bếp
Khi món nấu xong, thông báo cho nhân viên phục vụ và thu ngân:
// TODO: Implement Observer pattern
// - MonXongObserver
// - NhanVienPhucVu
// - ThuNgan
⚠️ Những Lỗi Đầu Bếp Thường Gặp
Lỗi 1: Lạm Dụng Pattern
// ❌ SAI: Dùng pattern khi không cần
type SimpleCalculator struct{}
// Không cần Factory cho cái này!
func CalculatorFactory() *SimpleCalculator {
    return &SimpleCalculator{}
}
// ✅ ĐÚNG: Simple problem, simple solution
calc := &SimpleCalculator{}
🔧 Cách sửa: Chỉ dùng pattern khi thực sự cần thiết. Đừng phức tạp hóa vấn đề đơn giản!
Lỗi 2: Singleton Không Thread-Safe
// ❌ SAI: Không an toàn với goroutines
var instance *Config
func GetConfig() *Config {
    if instance == nil {
        instance = &Config{} // Race condition!
    }
    return instance
}
// ✅ ĐÚNG: Dùng sync.Once
var once sync.Once
func GetConfig() *Config {
    once.Do(func() {
        instance = &Config{}
    })
    return instance
}
Lỗi 3: Decorator Quá Nhiều Lớp
// ❌ SAI: Quá nhiều decorator → khó debug
mon := &ThemA{&ThemB{&ThemC{&ThemD{&ThemE{&MonCoBan{}}}}}}
// ✅ ĐÚNG: Vừa đủ, có ý nghĩa
mon := &ThemTrung{&ThemTai{&PhoCoBan{}}}
💡 Bí Quyết Của Đầu Bếp
- 
KISS - Keep It Simple: Pattern là công cụ, không phải mục đích. Giải pháp đơn giản nhất thường là tốt nhất! 
- 
Hiểu vấn đề trước khi áp dụng: Mỗi pattern giải quyết một vấn đề cụ thể. Đừng "búa tìm đinh"! 
- 
Học từ code thực tế: Đọc code của các library nổi tiếng (Docker, Kubernetes) để thấy cách họ dùng patterns. 
- 
Tài liệu rõ ràng: Khi dùng pattern, comment giải thích tại sao dùng pattern đó. 
- 
Refactor dần dần: Không cần dùng pattern từ đầu. Code đơn giản → thấy vấn đề → refactor sang pattern. 
🎓 Bạn Đã Học Được
- ✅ Singleton: Đảm bảo chỉ có 1 instance duy nhất (config, logger)
- ✅ Factory: Tạo object mà không cần biết chi tiết implementation
- ✅ Observer: Thông báo tự động cho nhiều observers khi có sự kiện
- ✅ Decorator: Thêm tính năng động cho object mà không sửa code gốc
- ✅ Strategy: Đổi thuật toán runtime, tránh if-else phức tạp
- ✅ Builder: Xây dựng object phức tạp từng bước, dễ đọc dễ maintain
🍜 Món Tiếp Theo
Đã biết các phong cách nấu! Giờ học cách nấu nhanh hơn, hiệu quả hơn:
💡 Lời Khuyên Cuối: Design Patterns như công thức của đầu bếp lão làng - học từ kinh nghiệm, áp dụng khôn ngoan, đừng lạm dụng. Code tốt nhất là code dễ hiểu, không phải code "ngầu" nhất!