hocvietcode.com
  • Trang chủ
  • Học lập trình
    • Lập trình C/C++
    • Lập trình HTML
    • Lập trình Javascript
      • Javascript cơ bản
      • ReactJS framework
      • AngularJS framework
      • Typescript cơ bản
      • Angular
    • Lập trình Mobile
      • Lập Trình Dart Cơ Bản
        • Dart Flutter Framework
    • Cơ sở dữ liệu
      • MySQL – MariaDB
      • Micrsoft SQL Server
      • Extensible Markup Language (XML)
      • JSON
    • Lập trình PHP
      • Lập trình PHP cơ bản
      • Laravel Framework
    • Lập trình Java
      • Java Cơ bản
    • Cấu trúc dữ liệu và giải thuật
    • Lập Trình C# Cơ Bản
    • Machine Learning
  • WORDPRESS
    • WordPress cơ bản
    • WordPress nâng cao
    • Chia sẻ WordPress
  • Kiến thức hệ thống
    • Microsoft Azure
    • Docker
    • Linux
  • Chia sẻ IT
    • Tin học văn phòng
      • Microsoft Word
      • Microsoft Excel
    • Marketing
      • Google Adwords
      • Facebook Ads
      • Kiến thức khác
    • Chia sẻ phần mềm
    • Review công nghệ
    • Công cụ – tiện ích
      • Kiểm tra bàn phím online
      • Kiểm tra webcam online
Đăng nhập
  • Đăng nhập / Đăng ký

Please enter key search to display results.

Home
  • Phân tích thiết kế triển khai phần mềm
Mẫu thiết kế (Design Pattern)

Mẫu thiết kế (Design Pattern)

  • 18-06-2025
  • Toanngo92
  • 0 Comments

Mục lục

  • Giới thiệu – Vì sao cần Mẫu Thiết Kế (Design Patterns)?
    • 🧠 1.1. Từ thiết kế hệ thống đến kỹ thuật triển khai thông minh
    • 📌 1.2. Design Pattern là gì?
    • 🔧 1.3. Mẫu thiết kế không phải là đoạn mã copy-paste
    • 🛠 1.4. Lý do cần học mẫu thiết kế
    • 🧩 1.5. Khi nào bạn biết mình cần đến một Design Pattern?
    • 📚 1.6. Mẫu thiết kế phổ biến đến từ đâu?
    • 💬 Ghi nhớ chương này:
  • Creational design pattern
  • Mẫu thiết kế Factory (Factory Pattern)
    • 🎯 2.1. Mục tiêu của Factory Pattern là gì?
    • 🔍 2.2. Khi nào nên dùng Factory Pattern?
    • 🧠 2.3. Cấu trúc tổng quát của Factory Pattern
    • 🧪 2.4. Ví dụ: Vẽ hình
    • ✅ 2.5. Lợi ích chính của Factory Pattern
    • ⚠️ 2.6. Cảnh báo khi dùng Factory
    • 📌 2.7. So sánh Factory và khởi tạo trực tiếp
    • 💬 Ghi nhớ chương này:
  • Abstract Factory
    • 3.1. Vấn đề: Khi Factory chưa đủ
    • 🧭 3.2. Mục tiêu của Abstract Factory
    • 🧱 3.3. Cấu trúc tổng quát
    • 🧪 3.4. Ví dụ: Giao diện người dùng nhiều nền tảng
      • Giao diện chung:
      • Hai lớp cụ thể:
      • Các Factory:
      • Sử dụng:
    • ✅ 3.5. Lợi ích của Abstract Factory
    • ⚠️ 3.6. Khi nào KHÔNG nên dùng Abstract Factory?
    • 🔄 3.7. Factory vs Abstract Factory
    • 📦 3.8. Ứng dụng thực tế của Abstract Factory
    • 💬 Ghi nhớ chương này:
  • Khi nào dùng các mẫu tạo đối tượng (Creational Patterns)
    • 🧠 4.1. Mục tiêu của nhóm Creational Patterns
    • 🧭 4.2. Tổng quan các mẫu Creational phổ biến
    • 📊 4.3. So sánh nhanh các mẫu Factory
    • 🧪 4.4. Khi nào dùng Factory vs Abstract Factory?
    • 📦 4.5. Vị trí trong hệ thống phần mềm
    • 💬 4.6. Ghi nhớ chương này:
  • Singleton Pattern
    • 🧠 5.1. Vấn đề đặt ra
    • 🎯 5.2. Mục tiêu của Singleton
    • 🧱 5.3. Cấu trúc cơ bản của Singleton (Java/C#)
    • ⚙️ 5.4. Singleton có gì đặc biệt?
    • 🔐 5.5. Các biến thể nâng cao
    • 🧪 5.6. Ví dụ ứng dụng thực tế của Singleton
    • ⚠️ 5.7. Những nhược điểm và lưu ý
    • 📌 5.8. So sánh Singleton và Static class
    • 💬 Ghi nhớ chương này:
  • Builder Pattern
    • 🎯 6.1. Vấn đề: Khi constructor quá phức tạp
    • 🧠 6.2. Builder Pattern giải quyết điều gì?
    • 🧾 6.3. Cấu trúc Builder tổng quát
    • ✍️ 6.4. Ví dụ Java đơn giản
    • ✅ 6.5. Lợi ích của Builder Pattern
    • ⚠️ 6.6. Cẩn trọng khi dùng Builder
    • 🔁 6.7. Builder vs Factory vs Abstract Factory
    • 📦 6.8. Ứng dụng thực tế của Builder
    • 💬 Ghi nhớ chương này:
  • Prototype Pattern
    • 🎯 7.1. Vấn đề cần giải quyết
    • ✅ 7.2. Giải pháp: Prototype Pattern
    • 📦 7.3. Tình huống phù hợp để dùng Prototype
    • 🧱 7.4. Cấu trúc Prototype Pattern
    • ✍️ 7.5. Ví dụ đơn giản trong Java
    • ⚠️ 7.6. Shallow vs Deep Copy
    • 💡 7.7. Ưu điểm của Prototype Pattern
    • ❌ 7.8. Nhược điểm / Cảnh báo
    • 🧾 7.9. So sánh Prototype với các mẫu khác
    • 💬 Ghi nhớ chương này:
  • Hệ thống hóa nhóm Mẫu Thiết Kế Khởi Tạo (Creational Patterns)
    • 🎯 8.1. Mục tiêu của các mẫu khởi tạo
    • 📚 8.2. Bảng tổng hợp 5 mẫu khởi tạo
    • 🧱 8.3. So sánh theo khía cạnh kỹ thuật
    • 📌 8.4. Câu hỏi định hướng lựa chọn
    • 🧠 8.5. Kết luận định hướng sử dụng
    • 💬 Ghi nhớ chương này:
  • Nhóm Structural Patterns
    • 🧠 9.1. Structural Patterns là gì?
    • 🎯 9.2. Mục tiêu của nhóm Structural Patterns
    • 🧭 9.3. Khi nào bạn cần Structural Patterns?
    • 📚 9.4. Các mẫu Structural Patterns phổ biến
    • 🧩 9.5. Mối quan hệ với các nhóm còn lại
    • 🔧 9.6. Ví dụ thực tế áp dụng Structural Patterns
    • 💬 Ghi nhớ chương này:
  • Adapter Pattern
    • 🎯 10.1. Mục tiêu của Adapter Pattern
    • 🧠 10.2. Tư duy thực tế: ổ cắm điện
    • 🧩 10.3. Cấu trúc tổng quát của Adapter Pattern
    • ✍️ 10.4. Ví dụ Java – Adapter cho máy in PDF
      • Giao diện mong muốn (Target):
      • Lớp có sẵn (Adaptee):
      • Adapter:
      • Sử dụng:
    • 🧾 10.5. Các kiểu Adapter
    • ✅ 10.6. Lợi ích của Adapter Pattern
    • ⚠️ 10.7. Cảnh báo và hạn chế
    • 📚 10.8. Tình huống sử dụng thực tế
    • 💬 Ghi nhớ chương này:
  • Decorator Pattern
    • 🎯 11.1. Mục tiêu của Decorator Pattern
    • 🧠 11.2. Vấn đề: kế thừa không đủ linh hoạt
    • 🧩 11.3. Cấu trúc Decorator Pattern
    • ✍️ 11.4. Ví dụ Java: Trang trí cà phê
      • Giao diện:
      • Cà phê gốc:
      • Decorator trừu tượng:
      • Thêm đường:
      • Sử dụng:
    • ✅ 11.5. Ưu điểm của Decorator
    • ⚠️ 11.6. Nhược điểm và lưu ý
    • 📚 11.7. Tình huống thực tế
    • 🔄 11.8. Decorator vs Inheritance
    • 💬 Ghi nhớ chương này:
  • Composite Pattern
    • 🎯 12.1. Vấn đề: Đối tượng phân cấp phức tạp và khó quản lý
    • ✅ 12.2. Mục tiêu của Composite Pattern
    • 🧠 12.3. Ý tưởng: “Thành phần và tập hợp đều cùng một loại”
    • 📦 12.4. Cấu trúc tổng quát Composite Pattern
    • ✍️ 12.5. Ví dụ Java: Cây thư mục
      • Giao diện chung:
      • File đơn lẻ (Leaf):
      • Folder chứa file (Composite):
      • Sử dụng:
    • 📊 12.6. Lợi ích của Composite Pattern
    • ⚠️ 12.7. Cảnh báo khi dùng Composite
    • 📚 12.8. Ứng dụng thực tế
    • 💬 Ghi nhớ chương này:
  • Facade Pattern
    • 🎯 13.1. Vấn đề thực tế
    • ✅ 13.2. Mục tiêu của Facade Pattern
    • 🧠 13.3. Hình ảnh thực tế: Lễ tân khách sạn
    • 🧱 13.4. Cấu trúc tổng quát
    • ✍️ 13.5. Ví dụ Java – Facade cho hệ thống giải trí tại gia
      • Các subsystem:
      • Facade:
      • Sử dụng:
    • 📈 13.6. Lợi ích của Facade Pattern
    • ⚠️ 13.7. Nhược điểm và cảnh báo
    • 📚 13.8. Tình huống sử dụng thực tế
    • 💬 Ghi nhớ chương này:
  • Proxy Pattern
    • 🎯 14.1. Vấn đề: Không phải lúc nào cũng nên truy cập trực tiếp đối tượng thật
    • ✅ 14.2. Mục tiêu của Proxy Pattern
    • 🧠 14.3. Proxy ≠ Adapter
    • 🧱 14.4. Cấu trúc tổng quát
    • ✍️ 14.5. Ví dụ Java – Proxy cho hình ảnh
      • Interface:
      • Hình ảnh thật:
      • Proxy:
      • Sử dụng:
    • 📚 14.6. Các loại Proxy phổ biến
    • ✅ 14.7. Lợi ích của Proxy Pattern
    • ⚠️ 14.8. Nhược điểm và lưu ý
    • 📦 14.9. Ứng dụng thực tế của Proxy
    • 💬 Ghi nhớ chương này:
  • Nhóm Behavioral Patterns (Mẫu Thiết Kế Hành Vi)
    • 🎯 15.1. Mục tiêu của nhóm Behavioral Patterns
    • 🤹 15.2. Tại sao cần nhóm này?
    • 📚 15.3. Các mẫu thuộc nhóm Behavioral Patterns
    • 🧭 15.4. Mục tiêu lớn nhất: Tách “ai gọi” và “ai thực hiện”
    • 📌 15.5. Ứng dụng thực tế của nhóm này
    • 🧠 15.6. Các mẫu hành vi thường kết hợp tốt với Structural & Creational
    • 💬 Ghi nhớ chương này:
  • Observer Pattern – Một thay đổi, nhiều phản ứng
    • 🎯 16.1. Mục tiêu của Observer Pattern
    • 🧠 16.2. Ý tưởng: đăng ký và tự động nhận thông báo
    • 🧩 16.3. Cấu trúc tổng quát
    • ✍️ 16.4. Ví dụ Java – Cảnh báo nhiệt độ
      • Subject (sensor nhiệt độ):
      • Observer:
      • Cảm biến nhiệt:
      • Màn hình hiển thị:
      • Sử dụng:
    • ✅ 16.5. Lợi ích của Observer Pattern
    • ⚠️ 16.6. Nhược điểm & lưu ý
    • 📦 16.7. Ứng dụng thực tế
    • 💬 Ghi nhớ chương này:
  • Strategy Pattern – Hoán đổi thuật toán linh hoạt
    • 🎯 17.1. Vấn đề thực tế: nhiều thuật toán, không muốn if-else
    • ✅ 17.2. Mục tiêu của Strategy Pattern
    • 🧩 17.3. Cấu trúc tổng quát
    • ✍️ 17.4. Ví dụ Java – Tính phí vận chuyển
      • Strategy:
      • Các thuật toán cụ thể:
      • Context:
      • Sử dụng:
    • ✅ 17.5. Lợi ích của Strategy Pattern
    • ⚠️ 17.6. Cảnh báo khi dùng Strategy
    • 📚 17.7. Tình huống thực tế
    • 🔁 17.8. So sánh Strategy vs Template Method
    • 💬 Ghi nhớ chương này:
  • Command Pattern
    • 🎯 18.1. Vấn đề: Không muốn gọi phương thức trực tiếp
    • ✅ 18.2. Mục tiêu của Command Pattern
    • 🧱 18.3. Cấu trúc tổng quát
    • ✍️ 18.4. Ví dụ Java – Remote điều khiển đèn
      • Interface:
      • Receiver:
      • ConcreteCommand:
      • Invoker (remote control):
      • Sử dụng:
    • ✅ 18.5. Lợi ích của Command Pattern
    • ⚠️ 18.6. Cảnh báo khi dùng
    • 📚 18.7. Tình huống sử dụng thực tế
    • 🔁 18.8. So sánh Strategy vs Command
    • 💬 Ghi nhớ chương này:
  • Template Method Pattern
    • 🎯 19.1. Vấn đề: Khi quy trình tổng thể giống nhau, nhưng chi tiết mỗi bước khác nhau
    • ✅ 19.2. Mục tiêu của Template Method Pattern
    • 🧱 19.3. Cấu trúc tổng quát
    • ✍️ 19.4. Ví dụ Java – Xuất báo cáo
      • Lớp cha:
      • Lớp con: PDF
      • Lớp con: Excel
      • Sử dụng:
    • ✅ 19.5. Lợi ích của Template Method
    • ⚠️ 19.6. Nhược điểm & lưu ý
    • 🔁 19.7. So sánh Template Method vs Strategy
    • 📚 19.8. Tình huống thực tế
    • 💬 Ghi nhớ chương này:
  • State Pattern
    • 🎯 20.1. Vấn đề thực tế: hành vi thay đổi theo trạng thái
    • ✅ 20.2. Mục tiêu của State Pattern
    • 🧩 20.3. Cấu trúc tổng quát
    • ✍️ 20.4. Ví dụ Java – Máy ATM
      • Giao diện trạng thái:
      • Context:
      • Một trạng thái cụ thể:
    • ✅ 20.5. Lợi ích của State Pattern
    • ⚠️ 20.6. Nhược điểm & lưu ý
    • 📚 20.7. Tình huống sử dụng thực tế
    • 🔁 20.8. So sánh State vs Strategy
    • 💬 Ghi nhớ chương này:
  • Chain of Responsibility Pattern
    • 🎯 21.1. Vấn đề: nhiều handler, nhưng không biết ai xử lý
    • ✅ 21.2. Mục tiêu của Chain of Responsibility Pattern
    • 🧱 21.3. Cấu trúc tổng quát
    • ✍️ 21.4. Ví dụ Java – Xử lý đơn nghỉ phép
      • Giao diện handler:
      • Các handler cụ thể:
      • Sử dụng:
    • ✅ 21.5. Lợi ích của Chain of Responsibility
    • ⚠️ 21.6. Cảnh báo và nhược điểm
    • 📚 21.7. Ứng dụng thực tế
    • 🔁 21.8. So sánh với các mẫu khác
    • 💬 Ghi nhớ chương này:
  • Memento Pattern
    • 🎯 22.1. Vấn đề: làm sao để “quay lại như chưa từng xảy ra”
    • ✅ 22.2. Mục tiêu của Memento Pattern
    • 🧠 22.3. Cách hiểu đơn giản: “Bấm Ctrl + Z”
    • 🧱 22.4. Cấu trúc tổng quát
    • ✍️ 22.5. Ví dụ Java – Trình soạn thảo đơn giản
      • Memento:
      • Originator:
      • Caretaker:
      • Sử dụng:
    • ✅ 22.6. Lợi ích của Memento Pattern
    • ⚠️ 22.7. Hạn chế và lưu ý
    • 📚 22.8. Ứng dụng thực tế
    • 🔁 22.9. So sánh với các mẫu khác
    • 💬 Ghi nhớ chương này:
  • Visitor Pattern
    • 🎯 23.1. Vấn đề: Không thể hoặc không nên sửa class gốc
    • ✅ 23.2. Mục tiêu của Visitor Pattern
    • 🧱 23.3. Cấu trúc tổng quát
    • ✍️ 23.4. Ví dụ Java – Nhân sự và tính thuế
      • Interface Employee:
      • Các class:
      • Visitor:
      • Tính thuế:
      • Sử dụng:
    • ✅ 23.5. Lợi ích của Visitor Pattern
    • ⚠️ 23.6. Nhược điểm và lưu ý
    • 📚 23.7. Ứng dụng thực tế
    • 🔁 23.8. So sánh Visitor vs Strategy vs Command
    • 💬 Ghi nhớ chương này:
  • Mediator Pattern
    • 🎯 24.1. Vấn đề: Quá nhiều đối tượng gọi lẫn nhau gây rối
    • ✅ 24.2. Mục tiêu của Mediator Pattern
    • 🧱 24.3. Cấu trúc tổng quát
    • ✍️ 24.4. Ví dụ Java – Giao diện đơn giản
      • Mediator:
      • Component cha:
      • Component cụ thể:
      • ConcreteMediator:
      • Sử dụng:
    • ✅ 24.5. Lợi ích của Mediator Pattern
    • ⚠️ 24.6. Hạn chế và lưu ý
    • 📚 24.7. Ứng dụng thực tế
    • 🔁 24.8. So sánh Mediator vs Observer
    • 💬 Ghi nhớ chương này:
  • Interpreter Pattern
    • 🎯 25.1. Vấn đề: Cần phân tích và thực thi “ngôn ngữ riêng”
    • ✅ 25.2. Mục tiêu của Interpreter Pattern
    • 🧱 25.3. Cấu trúc tổng quát
    • ✍️ 25.4. Ví dụ Java – Biểu thức logic đơn giản
      • Interface:
      • Terminal:
      • Non-terminal:
      • Sử dụng:
    • ✅ 25.5. Lợi ích của Interpreter Pattern
    • ⚠️ 25.6. Nhược điểm và cảnh báo
    • 📚 25.7. Ứng dụng thực tế
    • 🔁 25.8. So sánh với các mẫu khác
    • 💬 Ghi nhớ chương này:

Giới thiệu – Vì sao cần Mẫu Thiết Kế (Design Patterns)?

🧠 1.1. Từ thiết kế hệ thống đến kỹ thuật triển khai thông minh

Ở những bài học trước, bạn đã học:

  • Phân tích yêu cầu
  • Xây dựng sơ đồ lớp
  • Mô hình hóa hành vi người dùng
  • Thiết kế sơ đồ trình tự và hoạt động

Những thứ đó giúp bạn hiểu hệ thống cần làm gì và có cấu trúc ra sao. Nhưng khi bắt tay vào viết mã, bạn sẽ thường xuyên gặp tình huống như:

“Làm sao khởi tạo object đúng cách mà không dùng if/else khắp nơi?”
“Làm sao để các phần của chương trình ít phụ thuộc vào nhau nhưng vẫn làm việc cùng nhau?”

Đây chính là lúc bạn cần đến Design Patterns – Mẫu thiết kế.


📌 1.2. Design Pattern là gì?

💬 Mẫu thiết kế là một giải pháp được kiểm chứng, có thể tái sử dụng, nhằm giải quyết một vấn đề thiết kế lặp lại trong phần mềm hướng đối tượng.

Bạn có thể hiểu mẫu thiết kế là:

  • Một công thức thiết kế đã được cộng đồng phần mềm sử dụng và cải tiến
  • Một giao thức giao tiếp giữa các lớp và đối tượng
  • Một cách để giải quyết vấn đề quen thuộc bằng tư duy trừu tượng

🔧 1.3. Mẫu thiết kế không phải là đoạn mã copy-paste

Điều quan trọng là:

  • Mẫu thiết kế không giống như thư viện có sẵn
  • Nó không thay thế việc phân tích, hiểu hệ thống
  • Mà là một chiến lược tổ chức mã để hệ thống dễ hiểu hơn, dễ mở rộng hơn, và ổn định hơn

🎯 Design Pattern giống như kiến trúc nhà – bạn vẫn phải tự xây, nhưng có mô hình sẵn để không “xây sai”.


🛠 1.4. Lý do cần học mẫu thiết kế

Lý doÝ nghĩa thực tế
Giải quyết vấn đề quen thuộcKhông phải phát minh lại bánh xe
Tăng tính mở rộng, bảo trìDễ thay đổi từng phần mà không vỡ toàn bộ
Tạo ra “từ vựng thiết kế”Dễ trao đổi trong nhóm: “Đây là pattern Factory”
Tăng kỹ năng kiến trúc phần mềmĐưa ra quyết định thiết kế cấp cao, chuẩn mực

🧩 1.5. Khi nào bạn biết mình cần đến một Design Pattern?

Nếu bạn thấy mình:

  • Viết nhiều if...else để tạo object
  • Phải thay đổi code ở nhiều file khác nhau khi thêm chức năng
  • Không kiểm soát được sự phụ thuộc giữa các class
  • Thường xuyên viết lại cùng một kiểu logic tạo đối tượng / quản lý trạng thái

→ Có thể bạn đang thiếu một mẫu thiết kế phù hợp.


📚 1.6. Mẫu thiết kế phổ biến đến từ đâu?

Hầu hết các mẫu thiết kế hiện đại được tổng hợp từ cuốn sách:

“Design Patterns: Elements of Reusable Object-Oriented Software”
(của nhóm “Gang of Four” – GOF: Gamma, Helm, Johnson, Vlissides)


💬 Ghi nhớ chương này:

  • Design Pattern là cách suy nghĩ, không phải đoạn mã
  • Được sinh ra từ kinh nghiệm thực tế, được chuẩn hóa thành kiến thức tái sử dụng
  • Là bước chuyển từ thiết kế kỹ thuật sang tư duy kiến trúc phần mềm

🧠 Học pattern không giúp bạn code nhanh hơn ngay lập tức – nhưng giúp bạn code đúng ngay từ đầu.

Creational design pattern

Mẫu thiết kế Factory (Factory Pattern)

🎯 2.1. Mục tiêu của Factory Pattern là gì?

Factory Pattern là mẫu thiết kế hướng đến việc tách biệt việc khởi tạo đối tượng khỏi phần sử dụng đối tượng đó.

Bạn không muốn mỗi lần tạo object lại phải viết:

javaCopyEditif (type.equals("Circle")) return new Circle();
else if (type.equals("Rectangle")) return new Rectangle();

Thay vào đó, bạn muốn:

javaCopyEditShape s = ShapeFactory.getShape("Circle");

→ Tối ưu hơn, dễ bảo trì hơn, dễ mở rộng hơn.


🔍 2.2. Khi nào nên dùng Factory Pattern?

Dấu hiệu bạn cần FactoryVì sao?
Bạn dùng nhiều new ở nhiều nơiDễ gây phụ thuộc, khó sửa
Có nhiều loại object cùng kế thừaVí dụ: Shape, Animal, PaymentMethod
Muốn ẩn cách object được tạo raGiúp bảo mật, kiểm soát
Muốn tái sử dụng logic khởi tạoTránh lặp code

🧠 2.3. Cấu trúc tổng quát của Factory Pattern

Thành phầnMô tả
Interface (Product)Giao diện chung cho các object được tạo
Concrete ClassCác lớp cụ thể thực thi interface
Factory ClassChứa phương thức để khởi tạo object theo yêu cầu

🧪 2.4. Ví dụ: Vẽ hình

Giả sử bạn có giao diện:

javaCopyEditpublic interface Shape {
    void draw();
}

Và các lớp triển khai:

javaCopyEditpublic class Circle implements Shape {
    public void draw() {
        System.out.println("Vẽ hình tròn");
    }
}

public class Rectangle implements Shape {
    public void draw() {
        System.out.println("Vẽ hình chữ nhật");
    }
}

Factory:

javaCopyEditpublic class ShapeFactory {
    public Shape getShape(String shapeType) {
        if (shapeType == null) return null;
        if (shapeType.equalsIgnoreCase("CIRCLE")) return new Circle();
        if (shapeType.equalsIgnoreCase("RECTANGLE")) return new Rectangle();
        return null;
    }
}

Sử dụng:

javaCopyEditShapeFactory factory = new ShapeFactory();
Shape s = factory.getShape("CIRCLE");
s.draw(); // Output: Vẽ hình tròn

✅ 2.5. Lợi ích chính của Factory Pattern

Lợi íchGiải thích
Tách rời logic khởi tạoGiảm phụ thuộc giữa module
Dễ mở rộngThêm class mới không cần sửa code gọi
Giảm lặp codeLogic khởi tạo tập trung tại một nơi
Áp dụng đa hìnhSử dụng chung một kiểu interface

⚠️ 2.6. Cảnh báo khi dùng Factory

  • Đừng tạo Factory quá đơn giản, rồi vẫn quản lý kiểu (type) bằng if/else ở nơi khác
  • Nếu chỉ có 1–2 loại class thì Factory có thể làm mọi thứ trở nên rườm rà không cần thiết
  • Factory không thay thế cho hiểu biết nghiệp vụ – bạn vẫn phải biết mình đang tạo cái gì

📌 2.7. So sánh Factory và khởi tạo trực tiếp

Tiêu chínew trực tiếpFactory
Dễ dùng với project nhỏ✅❌
Mở rộng dễ khi thêm class mới❌✅
Dễ kiểm soát việc tạo object❌✅
Tái sử dụng logic tạo❌✅
Có thể thay thế class bằng config❌✅

💬 Ghi nhớ chương này:

  • Factory Pattern giúp bạn tổ chức mã khởi tạo đối tượng một cách hợp lý
  • Nó rất hữu ích khi:
    • Có nhiều object cùng giao diện
    • Muốn ẩn đi chi tiết tạo object
  • Đừng lạm dụng – chỉ nên dùng khi thấy mình viết nhiều new hoặc if/else

🔑 Một hệ thống tốt là hệ thống mà nơi “biết cách tạo ra đối tượng” được kiểm soát chặt – Factory Pattern giúp bạn làm điều đó.


Abstract Factory

Khi bạn cần nhiều factory cho nhiều loại đối tượng

3.1. Vấn đề: Khi Factory chưa đủ

Factory Pattern giúp bạn:

  • Tạo object dựa trên type
  • Dễ thay đổi, dễ mở rộng

Nhưng điều gì xảy ra nếu:

Bạn không chỉ tạo một đối tượng, mà là nhiều đối tượng liên quan với nhau?

Ví dụ:

  • Giao diện trên Windows và Mac có:
    • Button
    • Textbox
    • Checkbox

→ Bạn không muốn dùng if để chọn cho từng loại → mà cần chọn cả bộ giao diện một cách nhất quán.

→ Đây là lúc bạn cần Abstract Factory Pattern.


🧭 3.2. Mục tiêu của Abstract Factory

Cung cấp một giao diện để tạo ra một nhóm đối tượng liên quan mà không chỉ rõ lớp cụ thể.

Bạn có thể:

  • Tạo WindowsFactory tạo ra WindowsButton, WindowsTextbox
  • Tạo MacFactory tạo ra MacButton, MacTextbox

→ Gọi đến cùng interface UIFactory, nhưng ra “bộ sản phẩm” khác nhau.


🧱 3.3. Cấu trúc tổng quát

Thành phầnMô tả
AbstractFactoryInterface/khuôn mẫu của tất cả factory
ConcreteFactoryFactory thực sự – ví dụ: WinFactory, MacFactory
AbstractProductInterface chung cho sản phẩm (Button, Textbox)
ConcreteProductSản phẩm cụ thể (WinButton, MacButton)

🧪 3.4. Ví dụ: Giao diện người dùng nhiều nền tảng

Giao diện chung:

javaCopyEditinterface Button {
    void paint();
}

interface GUIFactory {
    Button createButton();
}

Hai lớp cụ thể:

javaCopyEditclass WinButton implements Button {
    public void paint() {
        System.out.println("Windows Button");
    }
}

class MacButton implements Button {
    public void paint() {
        System.out.println("Mac Button");
    }
}

Các Factory:

javaCopyEditclass WinFactory implements GUIFactory {
    public Button createButton() {
        return new WinButton();
    }
}

class MacFactory implements GUIFactory {
    public Button createButton() {
        return new MacButton();
    }
}

Sử dụng:

javaCopyEditGUIFactory factory = OS.isMac() ? new MacFactory() : new WinFactory();
Button button = factory.createButton();
button.paint(); // Hiển thị theo hệ điều hành

✅ 3.5. Lợi ích của Abstract Factory

Lợi íchTác dụng
Tạo nhiều sản phẩm đồng bộKhông lo sai loại khi trộn nền tảng
Thay đổi toàn bộ “bộ giao diện” dễ dàngThay factory là đổi toàn bộ sản phẩm
Tăng khả năng mở rộngDễ thêm LinuxFactory, DarkThemeFactory
Tách biệt logic khởi tạoKhông cần if...else khắp nơi

⚠️ 3.6. Khi nào KHÔNG nên dùng Abstract Factory?

Dấu hiệuVì sao không nên dùng
Chỉ có 1–2 loại sản phẩmMẫu này làm mọi thứ rối hơn
Hệ thống đơn giản, không cần đồng bộDùng Factory đơn giản là đủ
Không có logic tạo động phức tạpKhông cần thêm abstract layer

🔄 3.7. Factory vs Abstract Factory

So sánhFactoryAbstract Factory
Tạo raMột đối tượngMột nhóm đối tượng liên quan
Mức độ trừu tượngTrung bìnhCao
Dễ triển khaiDễ hơnPhức tạp hơn, cần nhiều class
Dễ mở rộng hệ thống lớnCó giới hạnRất tốt khi cần mở rộng theo “bộ sản phẩm”
Thay đổi theo hoàn cảnhKhóDễ – chỉ cần đổi factory

📦 3.8. Ứng dụng thực tế của Abstract Factory

  • Giao diện nhiều nền tảng: Windows, macOS, Android, Web
  • Chủ đề (theme): Dark, Light, High Contrast
  • Game: bộ quái vật theo level/map
  • Web CMS: thay đổi toàn bộ layout bằng switching builder/theme

💬 Ghi nhớ chương này:

  • Abstract Factory là nâng cấp của Factory khi bạn cần tạo nhiều object “cùng nhóm”
  • Rất mạnh cho hệ thống hỗ trợ nhiều phiên bản, giao diện, ngữ cảnh
  • Dễ áp dụng với các hệ thống lớn, đòi hỏi cấu hình linh hoạt

🔑 Nếu bạn cần chọn “gói sản phẩm” thay vì “một sản phẩm” → hãy chọn Abstract Factory.

Khi nào dùng các mẫu tạo đối tượng (Creational Patterns)

🧠 4.1. Mục tiêu của nhóm Creational Patterns

Nhóm Creational Patterns tập trung vào một vấn đề chung:

Làm thế nào để tạo ra object một cách linh hoạt, rõ ràng, dễ mở rộng, và đúng nguyên lý thiết kế?

Vì vậy, các mẫu trong nhóm này giúp:

  • Tách rời logic khởi tạo khỏi logic sử dụng
  • Dễ dàng chuyển đổi cấu hình hoặc mở rộng hệ thống
  • Ẩn chi tiết khởi tạo phức tạp

🧭 4.2. Tổng quan các mẫu Creational phổ biến

MẫuMục tiêu chính
Factory MethodTạo object theo lựa chọn đầu vào
Abstract FactoryTạo một “bộ sản phẩm” đồng bộ
BuilderTạo object phức tạp theo từng bước
PrototypeNhân bản object từ bản mẫu có sẵn
SingletonĐảm bảo chỉ có 1 instance tồn tại toàn hệ thống

📊 4.3. So sánh nhanh các mẫu Factory

Tiêu chíFactory MethodAbstract Factory
Tạo ra1 object đơn lẻBộ object liên quan
Mức độ trừu tượngTrung bìnhCao hơn
Phù hợp khiCó nhiều loại sản phẩm khác nhauCó nhiều nhóm sản phẩm (UI theo theme…)
Dễ triển khai✅❌ (nhiều lớp hơn)
Dễ thay đổi toàn bộ sản phẩm❌ (chỉ từng cái)✅ (chỉ đổi factory)
Ví dụShapeFactory → CircleGUIFactory → WinButton + WinTextbox

🧪 4.4. Khi nào dùng Factory vs Abstract Factory?

Tình huốngMẫu phù hợp
Chọn một đối tượng từ danh sách (new A, new B, new C)✅ Factory
Cần tạo một bộ object thống nhất (cùng kiểu, cùng theme)✅ Abstract Factory
Muốn chọn cấu hình linh hoạt toàn hệ thống (UI, bản đồ,…)✅ Abstract Factory
Cần gói gọn logic tạo đối tượng, tránh if...else✅ Factory
Chỉ có vài class, không cần mở rộng nhiều❌ Đừng dùng cả hai

📦 4.5. Vị trí trong hệ thống phần mềm

Thành phần hệ thốngMẫu thiết kế phù hợp
Giao diện theo nền tảngAbstract Factory
Plugin game (quái vật theo map)Abstract Factory / Factory
Quản lý phương thức thanh toánFactory / Strategy
Tạo đơn hàng, hóa đơn, hợp đồngBuilder (nếu phức tạp)
Cấu hình kết nối CSDLSingleton

💬 4.6. Ghi nhớ chương này:

  • Factory: dùng khi bạn có nhiều object cần khởi tạo linh hoạt
  • Abstract Factory: dùng khi bạn cần đổi cả nhóm object đồng bộ
  • Cả hai mẫu này đều giúp ẩn đi chi tiết khởi tạo, áp dụng Open/Closed Principle

🔑 Mẫu thiết kế tốt không nằm ở chỗ “áp dụng bao nhiêu”, mà ở việc áp dụng đúng lúc, đúng nơi.

Singleton Pattern

Một đối tượng duy nhất, kiểm soát toàn cục

🧠 5.1. Vấn đề đặt ra

Giả sử trong hệ thống bạn chỉ cần một đối tượng duy nhất tồn tại để:

  • Ghi log
  • Kết nối cơ sở dữ liệu
  • Truy cập cấu hình hệ thống
  • Quản lý tài nguyên (memory pool, thread pool…)

Bạn không muốn có 2 logger ghi chồng lên nhau
Bạn không muốn có 2 đối tượng kết nối trùng nhau đến cùng một database


🎯 5.2. Mục tiêu của Singleton

Đảm bảo rằng chỉ có một thể hiện duy nhất (instance) của một class tồn tại trong hệ thống, và cung cấp một điểm truy cập toàn cục đến nó.


🧱 5.3. Cấu trúc cơ bản của Singleton (Java/C#)

javaCopyEditpublic class Singleton {
    private static Singleton instance;

    private Singleton() {} // Constructor private

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

→ Bất cứ nơi nào trong hệ thống bạn gọi Singleton.getInstance() sẽ luôn nhận về cùng một object duy nhất.


⚙️ 5.4. Singleton có gì đặc biệt?

Kỹ thuật áp dụngMục đích
Constructor privateKhông cho phép new từ bên ngoài
Biến staticLưu thể hiện duy nhất
Phương thức staticCho phép gọi từ bất cứ đâu (global access)

🔐 5.5. Các biến thể nâng cao

Biến thể SingletonKhi nào dùng?
Thread-safe SingletonKhi hệ thống có đa luồng (multi-threading)
Lazy InitializationKhi muốn tạo instance chỉ khi cần
Eager InitializationTạo instance từ đầu chương trình
Enum Singleton (Java)Cách an toàn nhất trong Java

🧪 5.6. Ví dụ ứng dụng thực tế của Singleton

Tình huốngTại sao dùng Singleton?
LoggerGhi log từ nhiều nơi nhưng đồng nhất
ConfigManagerCấu hình chỉ tải một lần
DB Connection PoolQuản lý kết nối tập trung
CacheLưu dữ liệu dùng chung toàn hệ thống

⚠️ 5.7. Những nhược điểm và lưu ý

Rủi roHệ quả
Lạm dụng Singleton như biến toàn cụcLàm hệ thống khó test, rối phụ thuộc
Không thread-safeCó thể tạo nhiều instance ngoài ý muốn
Khó mock trong unit testGây khó khăn cho kiểm thử tự động

📌 Singleton tốt cho quản lý tài nguyên, nhưng không nên dùng để truyền dữ liệu hoặc làm nơi lưu trạng thái tạm thời.


📌 5.8. So sánh Singleton và Static class

Tiêu chíSingletonStatic Class
Có thể kế thừa?✅ Có❌ Không
Có thể triển khai interface?✅❌
Có thể tạo mock/test dễ hơn?✅❌
Luôn có sẵn?❌ (lười khởi tạo nếu cần)✅ (luôn tồn tại)
Linh hoạt hơn?✅❌

💬 Ghi nhớ chương này:

  • Singleton = một class, một đối tượng duy nhất
  • Hữu ích khi bạn muốn kiểm soát truy cập tài nguyên hoặc trạng thái toàn cục
  • Hạn chế:
    • Có thể gây phụ thuộc ngầm
    • Không dễ test nếu không cẩn thận
  • Dùng Singleton khi:
    • Dữ liệu hoặc logic chỉ nên tồn tại ở một nơi duy nhất
    • Bạn muốn kiểm soát việc tạo và sử dụng đối tượng đó

🔑 Singleton là pattern đơn giản nhất – nhưng nếu dùng không đúng, nó sẽ gây ra “gián điệp toàn cục” trong hệ thống.

Builder Pattern

Khi bạn cần xây dựng đối tượng phức tạp theo từng bước

🎯 6.1. Vấn đề: Khi constructor quá phức tạp

Giả sử bạn cần khởi tạo một object User như sau:

javaCopyEditUser u = new User("John", "Doe", 30, "Engineer", null, true, false, "Admin");

❌ Rối mắt
❌ Khó đọc
❌ Không biết đối số thứ 6, 7, 8 là gì
❌ Gặp bug nếu truyền sai thứ tự


🧠 6.2. Builder Pattern giải quyết điều gì?

Cho phép bạn xây dựng một đối tượng phức tạp theo từng bước rõ ràng, có thể thay đổi thứ tự gọi, và dễ đọc.

Giống như cách bạn:

javaCopyEditPizza p = new PizzaBuilder()
              .addCheese()
              .addPepperoni()
              .setSize("Large")
              .build();

🧾 6.3. Cấu trúc Builder tổng quát

Thành phầnVai trò
BuilderLớp trung gian – chứa các phương thức để set giá trị
ConcreteBuilderThực thi các bước cụ thể (có thể giống hoặc chính Builder)
ProductĐối tượng được tạo ra
Director (tuỳ chọn)Điều phối việc gọi các bước builder

✍️ 6.4. Ví dụ Java đơn giản

javaCopyEditpublic class User {
    private String name;
    private int age;
    private String role;

    // private constructor
    private User(UserBuilder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.role = builder.role;
    }

    public static class UserBuilder {
        private String name;
        private int age;
        private String role;

        public UserBuilder setName(String name) {
            this.name = name;
            return this;
        }

        public UserBuilder setAge(int age) {
            this.age = age;
            return this;
        }

        public UserBuilder setRole(String role) {
            this.role = role;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

Sử dụng:

javaCopyEditUser user = new User.UserBuilder()
                  .setName("Alice")
                  .setAge(28)
                  .setRole("Admin")
                  .build();

✅ 6.5. Lợi ích của Builder Pattern

Lợi íchGiải thích
Rõ ràng và dễ đọcNhìn thấy rõ từng bước thiết lập dữ liệu
Tránh constructor quá tảiKhông cần viết hàng loạt constructor với nhiều biến
Dễ kiểm soát logicCó thể thêm validate vào từng bước
Có thể tạo các phiên bản mặc địnhGiao diện builder dễ mở rộng
Hữu ích với object bất biếnVì mọi field được set qua builder, không cần setter public

⚠️ 6.6. Cẩn trọng khi dùng Builder

Tình huống không phù hợpLý do
Object chỉ có 1–2 fieldDùng builder là dư thừa
Không cần tính bất biến hoặc cấu hình nhiều bướcConstructor đơn giản là đủ
Quá nhiều logic kiểm tra ràng buộcNên tách riêng ra class Validator

🔁 6.7. Builder vs Factory vs Abstract Factory

Tiêu chíBuilderFactoryAbstract Factory
Tạo từng bước?✅ Có❌ Không❌ Không
Gắn với object phức tạp?✅ Phù hợp❌ Thường dùng cho object đơn giản✅ Dùng cho nhóm object liên quan
Có thể tái sử dụng config?✅❌✅
Dễ đọc với chuỗi method✅ (method chaining)❌❌

📦 6.8. Ứng dụng thực tế của Builder

  • Form đăng ký/phân quyền người dùng phức tạp
  • Tạo tài liệu, hóa đơn, báo cáo
  • Tạo query SQL / API request
  • Cấu hình UI thành phần nhiều bước
  • Trong game: xây map, nhân vật từ nhiều module nhỏ

💬 Ghi nhớ chương này:

  • Builder giúp bạn tạo object phức tạp từng bước một cách an toàn và rõ ràng
  • Tránh constructor quá dài hoặc sai thứ tự tham số
  • Áp dụng tốt khi:
    • Object có nhiều field tuỳ chọn
    • Cần kiểm soát quá trình khởi tạo
    • Cần API mạch lạc, dễ đọc, dễ test

🧱 Builder không chỉ tạo object – nó tạo sự rõ ràng trong tư duy lập trình hướng đối tượng.

Prototype Pattern

Tạo bản sao mà không cần “new” lại từ đầu

🎯 7.1. Vấn đề cần giải quyết

Bạn có một object phức tạp với nhiều cấu hình, ví dụ:

javaCopyEditDocument doc = new Document();
doc.setHeader(...);
doc.setFooter(...);
doc.setStyle(...);
doc.loadTemplates();

Và bạn muốn tạo nhiều phiên bản giống y chang, nhưng chỉ thay đổi một vài phần nhỏ?

→ Bạn không muốn khởi tạo lại từ đầu (vì mất thời gian, tài nguyên)
→ Bạn cũng không muốn viết lại cùng một đoạn cấu hình dài


✅ 7.2. Giải pháp: Prototype Pattern

Prototype Pattern cho phép bạn tạo bản sao (clone) của một object hiện có, và sau đó tùy chỉnh nếu cần.

Bạn không new đối tượng mới từ đầu, mà sao chép từ một bản mẫu có sẵn (prototype).


📦 7.3. Tình huống phù hợp để dùng Prototype

Tình huốngLý do nên dùng
Object có quá trình khởi tạo tốn kémClone sẽ nhanh hơn new và setup
Cần tạo nhiều bản sao chỉ khác chút ítTăng hiệu suất, giảm dư thừa
Cần lưu trữ và phục hồi trạng tháiDễ dàng sao chép & rollback

🧱 7.4. Cấu trúc Prototype Pattern

Thành phầnVai trò
Prototype InterfaceKhai báo phương thức clone()
Concrete PrototypeTriển khai clone(), chứa logic sao chép
ClientGọi clone() để tạo bản sao thay vì new

✍️ 7.5. Ví dụ đơn giản trong Java

javaCopyEditpublic abstract class Shape implements Cloneable {
    public int x, y;
    public String color;

    public Shape clone() throws CloneNotSupportedException {
        return (Shape) super.clone();
    }
}

public class Circle extends Shape {
    public int radius;
}

Sử dụng:

javaCopyEditCircle circle1 = new Circle();
circle1.x = 10;
circle1.y = 20;
circle1.radius = 30;

Circle circle2 = (Circle) circle1.clone();
circle2.x = 50; // chỉnh nhẹ khác đi

⚠️ 7.6. Shallow vs Deep Copy

Kiểu sao chépÝ nghĩa
Shallow CopyChỉ sao chép tham chiếu – dùng chung object con
Deep CopyTạo ra object mới hoàn toàn, không phụ thuộc bản gốc

Với object chứa các object khác → nên dùng deep copy để tránh lỗi ngầm


💡 7.7. Ưu điểm của Prototype Pattern

Ưu điểmGiải thích
Giảm chi phí khởi tạoClone nhanh hơn setup lại toàn bộ
Linh hoạtCó thể sao chép + chỉnh sửa khác biệt
Dễ mở rộngKhông cần tạo Factory cho từng loại
Phù hợp với các hệ thống đồ họaVẽ, thiết kế, bản mẫu, UI layout…

❌ 7.8. Nhược điểm / Cảnh báo

Vấn đềHệ quả
Khó kiểm soát nếu dùng shallow copyCó thể thay đổi lẫn nhau không mong muốn
Không rõ luồng nếu clone quá nhiềuGây rối trạng thái object
clone() trong Java không được khuyến khíchVì dễ sai nếu object có nhiều tầng

📌 Trong nhiều ngôn ngữ hiện đại, clone được thay thế bằng các thư viện serialization/deep copy chuyên biệt.


🧾 7.9. So sánh Prototype với các mẫu khác

MẫuĐặc điểm chính
FactoryKhởi tạo object mới từ input
AbstractFactoryTạo nhóm object liên quan
BuilderTạo object phức tạp theo bước
SingletonĐảm bảo chỉ có 1 instance tồn tại
PrototypeTạo object mới bằng cách clone từ mẫu đã có

💬 Ghi nhớ chương này:

  • Prototype là mẫu cho phép clone đối tượng để tái sử dụng dễ dàng
  • Phù hợp với các hệ thống có:
    • Quá trình setup object phức tạp
    • Nhiều phiên bản gần giống nhau
    • Tài nguyên hạn chế (không muốn khởi tạo lại)

🔁 Prototype = “copy thông minh”, chứ không phải “copy & paste”.

Hệ thống hóa nhóm Mẫu Thiết Kế Khởi Tạo (Creational Patterns)

🎯 8.1. Mục tiêu của các mẫu khởi tạo

Nhóm Creational Patterns tập trung vào “cách tạo đối tượng” sao cho:

  • Dễ quản lý
  • Linh hoạt theo cấu hình
  • Tối ưu hiệu suất
  • Dễ mở rộng, tái sử dụng

Khác với new ClassName() đơn giản, nhóm này áp dụng tư duy kiến trúc để tạo object phù hợp với hoàn cảnh, quy mô hệ thống.


📚 8.2. Bảng tổng hợp 5 mẫu khởi tạo

MẫuMục tiêu chínhDùng khi nào?Ví dụ thực tế
Factory MethodTạo object từ input, giấu đi newNhiều class cùng interface, cần phân loại theo typeHình vẽ: Circle, Rectangle, Star
Abstract FactoryTạo nhóm object cùng họ, đồng bộHệ thống cần thay đổi cả “bộ sản phẩm”Giao diện: Win/Mac/Dark Theme
SingletonChỉ tạo một object toàn cục duy nhấtQuản lý cấu hình, logger, database…Logger, CSDL, Config
BuilderTạo object phức tạp theo từng bướcObject nhiều thuộc tính tùy chọn, cần clarityPizza, User Form, SQL Builder
PrototypeTạo object bằng cách clone mẫu đã cóCần nhân bản nhanh object cấu hình sẵnUI Layout, File Template, GameUnit

🧱 8.3. So sánh theo khía cạnh kỹ thuật

Tiêu chíFactoryAbstract FactorySingletonBuilderPrototype
Giảm phụ thuộc (decoupling)✅✅❌✅✅
Linh hoạt mở rộng✅✅✅❌✅✅
Tạo object theo cấu hình✅✅❌✅✅
Khó hiểu đối với người mớiTrung bìnhCaoThấpTrung bìnhTrung bình
Hỗ trợ tạo object phức tạp❌❌❌✅✅✅
Cần nhiều classVừaCaoThấpTrung bìnhThấp–trung

📌 8.4. Câu hỏi định hướng lựa chọn

Bạn nên chọn mẫu nào khi gặp những tình huống sau?

Câu hỏiMẫu phù hợp
Có nhiều loại object nhưng chung 1 interface?Factory
Cần tạo ra “bộ sản phẩm” nhất quán?Abstract Factory
Muốn đảm bảo chỉ có một bản ghi log hệ thống?Singleton
Tạo ra một hóa đơn, user form có nhiều trường tùy chọn?Builder
Cần nhân bản object mẫu?Prototype

🧠 8.5. Kết luận định hướng sử dụng

Nếu bạn cần…Hãy chọn…
Ẩn việc khởi tạo, nhưng vẫn chọn được kiểu objectFactory
Ẩn cả nhóm object và logic tạo ra chúngAbstract Factory
Có một object duy nhất toàn hệ thốngSingleton
Dễ dàng tạo từng phần object (bước 1, bước 2…)Builder
Tạo nhanh bản sao của object đã tồn tạiPrototype

💬 Ghi nhớ chương này:

  • Creational Patterns = chiến lược để khởi tạo object đúng cách
  • Chúng giúp hệ thống:
    ✅ Giảm phụ thuộc
    ✅ Dễ mở rộng
    ✅ Tối ưu hiệu suất
    ✅ Tăng khả năng test

🎯 Không có mẫu nào tốt nhất – chỉ có mẫu phù hợp nhất cho từng hoàn cảnh.

Nhóm Structural Patterns

🧠 9.1. Structural Patterns là gì?

Nếu nhóm Creational Patterns giúp bạn “tạo ra đối tượng đúng cách”, thì nhóm Structural Patterns sẽ giúp bạn:

Sắp xếp, liên kết và kết hợp các đối tượng/lớp một cách linh hoạt để tạo ra một hệ thống dễ hiểu, dễ mở rộng và có tổ chức.


🎯 9.2. Mục tiêu của nhóm Structural Patterns

  • Tăng cường khả năng kết hợp giữa các đối tượng và class
  • Tái sử dụng thành phần mà không phá vỡ cấu trúc
  • Cho phép bạn thêm chức năng mới mà không cần sửa đổi class gốc
  • Áp dụng nguyên lý mở–đóng (Open/Closed Principle) một cách rõ ràng

🧭 9.3. Khi nào bạn cần Structural Patterns?

Tình huốngCâu hỏi cần đặt ra
Cần tích hợp một class cũ vào hệ thống mớiLàm sao để “bọc” class đó mà không sửa code?
Muốn kết hợp nhiều object nhỏ thành 1 tổng thểCó mẫu nào giúp tạo “bộ phận tổng hợp”?
Cần trang trí đối tượng bằng tính năng mớiLàm sao để thêm tính năng mà không sửa gốc?
Giao diện cần chuyển đổi từ 1 API sang API khácLàm sao chuyển đổi định dạng tương thích?

📚 9.4. Các mẫu Structural Patterns phổ biến

MẫuMô tả ngắn gọn
AdapterChuyển đổi giao diện để tương thích
BridgeTách abstraction khỏi implementation
CompositeCấu trúc cây cho đối tượng phân cấp
DecoratorThêm tính năng cho object một cách linh hoạt
FacadeTạo giao diện đơn giản cho hệ thống phức tạp
FlyweightTái sử dụng object giống nhau để tiết kiệm bộ nhớ
ProxyĐại diện cho object khác, kiểm soát truy cập

🧩 9.5. Mối quan hệ với các nhóm còn lại

NhómMục tiêu chính
Creational PatternsTạo object hiệu quả, đúng cách
Structural PatternsTổ chức hệ thống, quản lý mối quan hệ giữa class
Behavioral PatternsXử lý tương tác & logic giữa object

→ Structural là “trung tâm” – giúp kết nối cái được tạo ra (Creational) với cách chúng hoạt động (Behavioral).


🔧 9.6. Ví dụ thực tế áp dụng Structural Patterns

Bài toánMẫu áp dụng
Dùng class cũ trong giao diện mớiAdapter
Giao diện đơn giản hóa hệ thống phức tạpFacade
Trang trí sản phẩm (pizza, đồ uống…)Decorator
Cấu trúc menu, thư mục, cây phân cấpComposite
Truy cập API thông qua bộ lọc kiểm traProxy

💬 Ghi nhớ chương này:

  • Structural Patterns giúp bạn quản lý sự phức tạp bằng tổ chức tốt
  • Là công cụ để:
    • Giao tiếp rõ ràng giữa thành phần
    • Kết hợp object/lớp mà không cần sửa nội dung bên trong
  • Phù hợp với hệ thống lớn, cần tái sử dụng, tùy biến, và giảm phụ thuộc

🧱 Structural Patterns = Kiến trúc sư tổ chức ngôi nhà phần mềm.

Adapter Pattern

Khi giao diện không khớp nhưng logic vẫn dùng được

🎯 10.1. Mục tiêu của Adapter Pattern

Adapter Pattern giúp bạn chuyển đổi giao diện của một lớp cũ hoặc không tương thích thành một giao diện mà hệ thống hiện tại có thể sử dụng được.

Bạn cần mẫu này khi:

  • Bạn có class cũ nhưng không thể sửa
  • Bạn dùng thư viện bên ngoài có API không giống với hệ thống bạn đang phát triển
  • Bạn muốn tái sử dụng logic mà không muốn (hoặc không thể) thay đổi cấu trúc gốc

🧠 10.2. Tư duy thực tế: ổ cắm điện

  • Bạn có máy sấy tóc mua từ Mỹ (phích 3 chấu)
  • Nhưng ổ điện ở Việt Nam là 2 chấu

→ Bạn không cần đổi máy sấy, chỉ cần một bộ chuyển đổi (adapter)


🧩 10.3. Cấu trúc tổng quát của Adapter Pattern

Thành phầnVai trò
TargetGiao diện mà client mong muốn sử dụng
AdapteeLớp có logic thực sự, nhưng giao diện không tương thích
AdapterLớp trung gian – “chuyển đổi” từ Target sang Adaptee
ClientGọi đến Target (nhưng được xử lý bởi Adaptee qua Adapter)

✍️ 10.4. Ví dụ Java – Adapter cho máy in PDF

Giao diện mong muốn (Target):

javaCopyEditinterface Printer {
    void print(String content);
}

Lớp có sẵn (Adaptee):

javaCopyEditclass PdfGenerator {
    public void generate(String data) {
        System.out.println("PDF: " + data);
    }
}

Adapter:

javaCopyEditclass PdfAdapter implements Printer {
    private PdfGenerator pdfGen = new PdfGenerator();

    public void print(String content) {
        pdfGen.generate(content);
    }
}

Sử dụng:

javaCopyEditPrinter printer = new PdfAdapter();
printer.print("Hello PDF"); // Gọi đúng interface, nhưng xử lý bên trong là PDF

🧾 10.5. Các kiểu Adapter

Kiểu AdapterCách triển khai
Object AdapterDùng composition – adapter chứa đối tượng gốc
Class AdapterDùng kế thừa – adapter mở rộng từ adaptee (Java: không phổ biến vì không hỗ trợ đa kế thừa)

✅ 10.6. Lợi ích của Adapter Pattern

Lợi íchGiải thích
Tái sử dụng code cũKhông cần sửa mã gốc
Kết nối thư viện bên ngoàiDễ tích hợp với hệ thống đang có
Tách biệt giao diện và xử lý bên trongTăng tính module hóa
Áp dụng Open/Closed PrincipleMở rộng mà không sửa lớp cũ

⚠️ 10.7. Cảnh báo và hạn chế

Vấn đềHệ quả
Tạo quá nhiều adapterGây phức tạp, khó theo dõi
Adapter quá dàyLàm giảm hiệu suất gọi hàm
Không biết logic thật chạy ở đâuDễ khiến người mới hiểu sai luồng xử lý

📚 10.8. Tình huống sử dụng thực tế

Tình huốngAdapter áp dụng thế nào?
Sử dụng class bên ngoài nhưng interface không khớpViết adapter cho thư viện
Dùng module Cũ trong hệ thống mớiAdapter giữ nguyên code cũ
Tích hợp API từ bên thứ baAdapter chuyển đổi đầu vào/ra

💬 Ghi nhớ chương này:

  • Adapter là cây cầu giữa giao diện bạn muốn và class bạn có
  • Giúp bạn:
    • Kết nối thư viện cũ với hệ thống mới
    • Giữ lại logic gốc nhưng “đổi đầu vào”
    • Làm việc với đa giao diện mà không lặp lại code

🔧 “Đừng vứt bỏ class không tương thích – hãy dùng Adapter để khiến nó hữu ích trở lại.”

Decorator Pattern

Gói thêm tính năng cho đối tượng mà không làm rối hệ thống

🎯 11.1. Mục tiêu của Decorator Pattern

Decorator cho phép bạn thêm tính năng mới cho một đối tượng đang hoạt động, mà không cần thay đổi code của lớp gốc.

  • ✅ Vẫn giữ object cũ (không sửa đổi)
  • ✅ Vẫn tuân thủ nguyên lý Open/Closed (mở rộng – không chỉnh sửa)
  • ✅ Có thể bọc nhiều lớp Decorator chồng lên nhau

🧠 11.2. Vấn đề: kế thừa không đủ linh hoạt

Giả sử bạn có class Coffee:

  • Bạn muốn có MilkCoffee, SugarCoffee, VanillaMilkSugarCoffee…

Nếu dùng kế thừa:

javaCopyEditclass Coffee → MilkCoffee → MilkSugarCoffee → MilkSugarVanillaCoffee → ...

→ ❌ Quá nhiều class kết hợp → nổ tổ hợp
→ ❌ Không thể thay đổi động lúc runtime


🧩 11.3. Cấu trúc Decorator Pattern

Thành phầnVai trò
Component (interface)Giao diện chung
ConcreteComponentLớp gốc (đối tượng chính)
Decorator (abstract)Lớp cha của các decorator – implement Component
ConcreteDecoratorLớp thêm tính năng cụ thể

✍️ 11.4. Ví dụ Java: Trang trí cà phê

Giao diện:

javaCopyEditinterface Coffee {
    String getDescription();
    double getCost();
}

Cà phê gốc:

javaCopyEditclass SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }
    public double getCost() {
        return 2.0;
    }
}

Decorator trừu tượng:

javaCopyEditabstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee c) {
        this.decoratedCoffee = c;
    }
}

Thêm đường:

javaCopyEditclass SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee c) {
        super(c);
    }

    public String getDescription() {
        return decoratedCoffee.getDescription() + ", Sugar";
    }

    public double getCost() {
        return decoratedCoffee.getCost() + 0.5;
    }
}

Sử dụng:

javaCopyEditCoffee c = new SugarDecorator(new SimpleCoffee());
System.out.println(c.getDescription()); // Simple Coffee, Sugar
System.out.println(c.getCost());        // 2.5

→ Bạn có thể tiếp tục bọc bằng MilkDecorator, VanillaDecorator…


✅ 11.5. Ưu điểm của Decorator

Ưu điểmLợi ích thực tế
Linh hoạt – thêm tính năng lúc runtimeCó thể bật/tắt tùy ý
Không thay đổi class gốcDễ bảo trì
Tránh kế thừa rối rắmKhông cần tạo class tổ hợp
Dễ kết hợp nhiều tính năngDecorator lồng nhau được

⚠️ 11.6. Nhược điểm và lưu ý

Hạn chếHệ quả
Tạo nhiều lớp Decorator nhỏ lẻGây rối nếu không đặt tên rõ ràng
Gọi get() quá nhiều tầngGây khó debug hoặc trace log
Không phù hợp với object đơn giảnDùng builder có thể hiệu quả hơn

📚 11.7. Tình huống thực tế

Tình huốngDùng Decorator để…
UI Button có border, shadow, hoverGói từng hiệu ứng riêng
Đồ uống (cà phê, trà, pizza…)Trang trí thêm topping
Dữ liệu: mã hóa, nén, logBọc từng tầng xử lý riêng
Logging hoặc security layerBọc quanh service thật

🔄 11.8. Decorator vs Inheritance

So sánhDecoratorKế thừa class
Linh hoạt runtime✅❌
Nổ tổ hợp class❌ (1 class Decorator dùng nhiều lần)✅
Dễ thay đổi, bật/tắt✅❌
Đơn giản cho object nhỏ❌ (nhiều lớp)✅

💬 Ghi nhớ chương này:

  • Decorator = “bọc thêm tính năng cho object mà không chạm vào class gốc”
  • Phù hợp khi:
    • Object có nhiều “topping” (tính năng phụ trợ)
    • Cần bật/tắt tính năng động tại runtime
  • Rất mạnh cho UI, dịch vụ, bảo mật, log, dữ liệu

🎁 Decorator không phá cấu trúc – nó “trang trí” đúng cách cho object bạn đang có.

Composite Pattern

Thiết kế hệ thống phân cấp dạng cây một cách đồng nhất

🎯 12.1. Vấn đề: Đối tượng phân cấp phức tạp và khó quản lý

Giả sử bạn xây dựng:

  • Một hệ thống menu đa cấp (Menu → Submenu → Item)
  • Một tổ chức công ty (Giám đốc → Trưởng phòng → Nhân viên)
  • Một cây thư mục (Folder → Subfolder → File)

Bạn cần xử lý tất cả phần tử trong cấu trúc đó theo cách giống nhau:

Ví dụ: menu.render() nên hoạt động với cả item lẫn submenu chứa nhiều item.

→ Nếu dùng if-else, bạn sẽ phân biệt từng loại đối tượng và logic xử lý rất rối.


✅ 12.2. Mục tiêu của Composite Pattern

Composite Pattern giúp bạn xử lý cấu trúc cây gồm các object có thể lồng nhau, và cho phép dùng cùng một interface để thao tác với tất cả phần tử.


🧠 12.3. Ý tưởng: “Thành phần và tập hợp đều cùng một loại”

  • File là một thành phần
  • Folder là một tập hợp chứa nhiều thành phần
  • Nhưng cả hai đều có thể được gọi getSize(), render(), delete()… như nhau

→ Bạn không cần biết mình đang xử lý gì – chỉ cần gọi đúng interface.


📦 12.4. Cấu trúc tổng quát Composite Pattern

Thành phầnVai trò
ComponentGiao diện chung (hàm add(), display()…)
LeafĐối tượng con đơn lẻ (không chứa gì)
CompositeĐối tượng chứa thành phần khác (danh sách Component)
ClientGọi tới interface Component và xử lý tất cả như nhau

✍️ 12.5. Ví dụ Java: Cây thư mục

Giao diện chung:

javaCopyEditinterface FileSystem {
    void ls();
}

File đơn lẻ (Leaf):

javaCopyEditclass File implements FileSystem {
    private String name;

    public File(String name) {
        this.name = name;
    }

    public void ls() {
        System.out.println("File: " + name);
    }
}

Folder chứa file (Composite):

javaCopyEditclass Folder implements FileSystem {
    private String name;
    private List<FileSystem> children = new ArrayList<>();

    public Folder(String name) {
        this.name = name;
    }

    public void add(FileSystem f) {
        children.add(f);
    }

    public void ls() {
        System.out.println("Folder: " + name);
        for (FileSystem child : children) {
            child.ls();
        }
    }
}

Sử dụng:

javaCopyEditFolder root = new Folder("root");
File a = new File("a.txt");
Folder sub = new Folder("sub");

sub.add(new File("b.txt"));
root.add(a);
root.add(sub);

root.ls();

📊 12.6. Lợi ích của Composite Pattern

Lợi íchGiải thích
Giao diện đồng nhấtDù là File hay Folder, bạn chỉ cần gọi ls()
Dễ mở rộngCó thể thêm loại mới như Shortcut, ImageFolder…
Không cần instanceof, if-elseGiảm phân nhánh logic
Phù hợp cho cấu trúc phân cấpMenu, tổ chức, UI component, v.v.

⚠️ 12.7. Cảnh báo khi dùng Composite

Hạn chếGợi ý
Có thể khó debug cây sâuIn log phân cấp hoặc sử dụng trình debug
Không hợp nếu không có lồng nhauChỉ dùng nếu bạn thực sự có cấu trúc phân cấp
Có thể rối nếu client vẫn phân biệt Leaf/CompositeĐảm bảo client chỉ xử lý qua Component

📚 12.8. Ứng dụng thực tế

Tình huốngComposite giúp gì?
Menu UI đa cấpRender giống nhau cho từng menu item
Tổ chức nhân sựLệnh getSalary() dùng được cho cả nhân viên và quản lý
File explorer, cây thư mụcGọi delete() hoặc download() cho folder hay file đều như nhau
Cây câu hỏi, form độngMỗi thành phần là 1 node có thể chứa phần tử con

💬 Ghi nhớ chương này:

  • Composite giúp bạn dùng một kiểu dữ liệu duy nhất để quản lý cả cấu trúc phức hợp và thành phần đơn
  • Rất phù hợp cho:
    • UI lồng nhau
    • Cây dữ liệu
    • Sơ đồ tổ chức
  • Ưu điểm: gọn gàng, dễ mở rộng, dễ lặp, không cần phân biệt kiểu thủ công

🌲 Composite Pattern = “Xem phần và tổng thể như nhau”.

Facade Pattern

Giấu đi phức tạp, lộ ra đơn giản

🎯 13.1. Vấn đề thực tế

Bạn có một hệ thống với nhiều thành phần:

  • Subsystem A, B, C, D…
  • Mỗi subsystem có hàng loạt class, method, bước xử lý…

Nếu để client gọi trực tiếp:

javaCopyEdita.init();
b.validate();
c.load();
d.finalizeProcess();

→ ❌ Quá phức tạp
→ ❌ Khó sử dụng lại
→ ❌ Khó bảo trì khi các bước thay đổi


✅ 13.2. Mục tiêu của Facade Pattern

Facade Pattern cung cấp một giao diện đơn giản và thống nhất, giúp client dễ sử dụng một hệ thống phức tạp mà không cần hiểu chi tiết nội bộ.


🧠 13.3. Hình ảnh thực tế: Lễ tân khách sạn

  • Bạn không cần trực tiếp làm việc với từng bộ phận (buồng phòng, kế toán, kỹ thuật…)
  • Bạn chỉ cần nói với lễ tân, họ sẽ xử lý tất cả phía sau.

→ Lễ tân = Facade
→ Các bộ phận trong khách sạn = Subsystems


🧱 13.4. Cấu trúc tổng quát

Thành phầnVai trò
FacadeGiao diện đơn giản cho client
SubsystemsCác lớp, module thực hiện công việc thật sự
ClientGọi Facade, không gọi trực tiếp subsystem

✍️ 13.5. Ví dụ Java – Facade cho hệ thống giải trí tại gia

Các subsystem:

javaCopyEditclass Amplifier {
    public void on() { System.out.println("Amp on"); }
}
class DVDPlayer {
    public void play(String movie) {
        System.out.println("Playing movie: " + movie);
    }
}
class Lights {
    public void dim() { System.out.println("Lights dimmed"); }
}

Facade:

javaCopyEditclass HomeTheaterFacade {
    private Amplifier amp;
    private DVDPlayer dvd;
    private Lights lights;

    public HomeTheaterFacade(Amplifier amp, DVDPlayer dvd, Lights lights) {
        this.amp = amp;
        this.dvd = dvd;
        this.lights = lights;
    }

    public void watchMovie(String movie) {
        lights.dim();
        amp.on();
        dvd.play(movie);
    }
}

Sử dụng:

javaCopyEditHomeTheaterFacade home = new HomeTheaterFacade(new Amplifier(), new DVDPlayer(), new Lights());
home.watchMovie("Inception");

→ Client chỉ gọi 1 hàm duy nhất thay vì thao tác với 3–4 subsystem.


📈 13.6. Lợi ích của Facade Pattern

Lợi íchMô tả
Giao diện gọn gàng cho hệ thống lớnClient không cần biết nội bộ subsystem
Dễ sử dụng lạiDễ viết API, wrapper, CLI
Giảm phụ thuộcGiữa client và logic bên trong
Có thể kết hợp với pattern khácFacade kết hợp tốt với Singleton, Adapter

⚠️ 13.7. Nhược điểm và cảnh báo

Hạn chếCảnh báo
Che giấu quá nhiều logic nội bộLàm client “mù mờ” nếu cần xử lý chi tiết
Không thay thế được thiết kế kémFacade không sửa được hệ thống phức tạp vô lý
Có thể bị lạm dụng thành “God class”Nếu chứa quá nhiều phương thức không liên quan

📚 13.8. Tình huống sử dụng thực tế

Tình huốngFacade giúp gì?
Giao diện lập trình với hệ thống cũTạo lớp trung gian API gọn gàng
Giao diện web gọi nhiều serviceDùng controller gọn gọi nhiều service logic
Module CLI hoặc REST API trung gianTạo lệnh đơn giản → gọi nhiều lớp xử lý
Dự án chia team: Frontend – BackendBackend cung cấp Facade → không cần biết chi tiết DAO

💬 Ghi nhớ chương này:

  • Facade Pattern = “cửa chính dễ dùng để bước vào một ngôi nhà phức tạp”
  • Dùng khi:
    • Hệ thống đã ổn định nhưng quá rối để dùng trực tiếp
    • Bạn cần cung cấp API hoặc giao diện rõ ràng
  • Giúp tăng:
    • Tính đóng gói
    • Dễ dùng lại
    • Dễ bảo trì

🎯 Facade không sửa thiết kế xấu – nhưng nó giúp bạn ẩn nó khỏi client.

Proxy Pattern

Bảo vệ, trì hoãn hoặc điều hướng truy cập đến đối tượng thật

🎯 14.1. Vấn đề: Không phải lúc nào cũng nên truy cập trực tiếp đối tượng thật

Ví dụ:

  • Bạn có một object kết nối cơ sở dữ liệu → không muốn khởi tạo ngay lập tức
  • Hoặc một tài nguyên nhạy cảm → chỉ user được phân quyền mới được truy cập
  • Hoặc một dịch vụ từ xa → cần đóng gói giao tiếp (như API, RPC)

→ Truy cập trực tiếp là nguy hiểm, tốn tài nguyên, khó kiểm soát


✅ 14.2. Mục tiêu của Proxy Pattern

Proxy Pattern cung cấp một đối tượng thay thế (proxy) cho đối tượng thật, cho phép:

  • Kiểm soát quyền truy cập
  • Trì hoãn khởi tạo
  • Ghi log, theo dõi
  • Đóng gói gọi từ xa (remote call)

🧠 14.3. Proxy ≠ Adapter

MẫuMục tiêu
AdapterLàm giao diện không tương thích → tương thích
ProxyKiểm soát hành vi truy cập, giả dạng đối tượng thật

🧱 14.4. Cấu trúc tổng quát

Thành phầnVai trò
SubjectInterface chung cho cả Proxy và RealSubject
RealSubjectĐối tượng thật (có logic xử lý)
ProxyLớp thay thế, kiểm soát truy cập đến RealSubject
ClientGọi qua Proxy như gọi object thật

✍️ 14.5. Ví dụ Java – Proxy cho hình ảnh

Interface:

javaCopyEditinterface Image {
    void display();
}

Hình ảnh thật:

javaCopyEditclass RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading " + filename);
    }

    public void display() {
        System.out.println("Displaying " + filename);
    }
}

Proxy:

javaCopyEditclass ProxyImage implements Image {
    private RealImage realImage;
    private String filename;

    public ProxyImage(String filename) {
        this.filename = filename;
    }

    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename); // Trì hoãn tải ảnh
        }
        realImage.display();
    }
}

Sử dụng:

javaCopyEditImage img = new ProxyImage("big_photo.jpg");
img.display(); // lần đầu: load + display
img.display(); // lần 2: chỉ display

📚 14.6. Các loại Proxy phổ biến

Loại ProxyCông dụng
Virtual ProxyTrì hoãn khởi tạo đối tượng tốn tài nguyên
Protection ProxyKiểm soát quyền truy cập (authorization)
Remote ProxyĐóng gói truy cập object ở nơi khác (API, RPC)
Smart ProxyGhi log, đếm số lần truy cập, cache, validate

✅ 14.7. Lợi ích của Proxy Pattern

Lợi íchGiải thích
Kiểm soát truy cậpHạn chế ai có quyền dùng object thật
Tối ưu hiệu suấtTrì hoãn hoặc dùng cache
Tăng tính mở rộngCó thể ghi log, bảo vệ mà không sửa object chính
Dễ thay thế / hoán đổi objectVì proxy có cùng interface

⚠️ 14.8. Nhược điểm và lưu ý

Hạn chếHậu quả
Gọi chậm hơn object gốcVì qua thêm một lớp
Rối khi có nhiều loại proxyKhó debug và bảo trì
Proxy có thể “ẩn” logicClient không biết thực sự đang gọi ai

📦 14.9. Ứng dụng thực tế của Proxy

Tình huốngÁp dụng
Giao diện hiển thị ảnh, video nặngVirtual Proxy
Truy cập file, database theo userProtection Proxy
Gọi dịch vụ từ xa (REST, gRPC)Remote Proxy
Xác thực tài khoảnProxy login/auth
Ghi log mọi lệnh gọi đến classSmart Proxy

💬 Ghi nhớ chương này:

  • Proxy = “đại diện có kiểm soát” cho đối tượng thật
  • Hữu ích khi:
    • Không muốn khởi tạo object ngay lập tức
    • Cần kiểm soát quyền truy cập
    • Cần đóng gói logic từ xa hoặc logic phụ trợ

🛡 Proxy không thay đổi logic chính – nó bảo vệ, trì hoãn, hoặc giám sát nó.

Nhóm Behavioral Patterns (Mẫu Thiết Kế Hành Vi)

🎯 15.1. Mục tiêu của nhóm Behavioral Patterns

Nhóm Behavioral Patterns tập trung vào việc quản lý luồng tương tác, giao tiếp và trách nhiệm giữa các đối tượng – không phải chúng được tạo ra như thế nào, mà là chúng hành xử ra sao.


🤹 15.2. Tại sao cần nhóm này?

Trong phần mềm thực tế:

  • Các đối tượng không làm việc độc lập, mà phối hợp với nhau
  • Cần quản lý:
    • Luồng dữ liệu (ai gọi ai, theo thứ tự nào)
    • Phân quyền hành động (ai chịu trách nhiệm)
    • Giao tiếp nhiều-đến-một (observer)
    • Hoán đổi hành vi linh hoạt (strategy)

→ Bạn không muốn các đối tượng gắn chặt vào nhau, vì:

  • Khó test
  • Khó mở rộng
  • Dễ gây lỗi domino nếu sửa một chỗ

📚 15.3. Các mẫu thuộc nhóm Behavioral Patterns

MẫuChức năng chính
ObserverMột thay đổi → nhiều đối tượng được thông báo
StrategyHoán đổi thuật toán linh hoạt tại runtime
CommandBiến hành động thành đối tượng độc lập
Template MethodXác định khung xử lý chung, cho phép lớp con điều chỉnh chi tiết
StateThay đổi hành vi theo trạng thái nội tại
Chain of ResponsibilityGửi yêu cầu qua chuỗi xử lý
IteratorDuyệt các phần tử mà không lộ cấu trúc
MediatorTrung tâm điều phối giao tiếp giữa object
VisitorThêm hành vi mới cho object mà không thay đổi class
InterpreterĐịnh nghĩa cú pháp và diễn giải ngôn ngữ đơn giản
MementoLưu và phục hồi trạng thái object (undo/redo)

🧭 15.4. Mục tiêu lớn nhất: Tách “ai gọi” và “ai thực hiện”

Các mẫu hành vi giúp:

  • Tách logic điều phối ra khỏi logic xử lý
  • Thay vì A gọi trực tiếp B, ta:
    • Giao tiếp qua Mediator, Command, Observer…
    • Hoặc truyền hành vi vào A (Strategy)

📌 15.5. Ứng dụng thực tế của nhóm này

Tình huốngMẫu phù hợp
Giao diện thay đổi khi model thay đổiObserver
Nút bấm trong giao diệnCommand
Nhiều thuật toán lọc sản phẩm khác nhauStrategy
Undo/Redo tài liệuMemento
Giao tiếp phức tạp trong gameMediator
Duyệt cây thư mục, form nhiều bướcIterator, State

🧠 15.6. Các mẫu hành vi thường kết hợp tốt với Structural & Creational

Ví dụ:

  • Observer + Composite → xử lý thay đổi trong cây UI
  • Command + Singleton → thực thi lệnh trung tâm
  • Strategy + Factory → tạo thuật toán theo cấu hình

💬 Ghi nhớ chương này:

  • Behavioral Patterns giúp hệ thống linh hoạt hơn khi phối hợp và xử lý hành vi
  • Giảm ràng buộc giữa object
  • Dễ test, dễ thay đổi
  • Giữ đúng nguyên lý SOLID, đặc biệt là:
    • Open/Closed Principle
    • Single Responsibility Principle

🤹 Mẫu hành vi không tạo thêm object – nó tạo thêm trật tự.

Observer Pattern – Một thay đổi, nhiều phản ứng

🎯 16.1. Mục tiêu của Observer Pattern

Cho phép một đối tượng (Subject) thông báo đến nhiều đối tượng khác (Observer) khi có sự thay đổi, mà không cần biết chi tiết ai đang lắng nghe.

Ứng dụng cực mạnh trong:

  • Giao diện UI
  • Mô hình MVC
  • Sự kiện hệ thống
  • Pub/Sub, socket, signal, thông báo real-time

🧠 16.2. Ý tưởng: đăng ký và tự động nhận thông báo

  • Giống như bạn bấm nút “Theo dõi” kênh YouTube
  • Khi có video mới, kênh đó không cần gọi bạn – hệ thống gửi thông báo tự động đến tất cả người theo dõi

→ Observer = Người theo dõi
→ Subject = Nguồn phát sự kiện


🧩 16.3. Cấu trúc tổng quát

Thành phầnVai trò
SubjectGiao diện cho việc thêm/xoá/thông báo observer
ConcreteSubjectĐối tượng thật, chứa dữ liệu và gọi notify()
ObserverGiao diện lắng nghe
ConcreteObserverĐối tượng thực tế thực thi cập nhật

✍️ 16.4. Ví dụ Java – Cảnh báo nhiệt độ

Subject (sensor nhiệt độ):

javaCopyEditinterface Subject {
    void addObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

Observer:

javaCopyEditinterface Observer {
    void update(double temperature);
}

Cảm biến nhiệt:

javaCopyEditclass TemperatureSensor implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private double temperature;

    public void setTemperature(double temp) {
        this.temperature = temp;
        notifyObservers();
    }

    public void addObserver(Observer o) { observers.add(o); }
    public void removeObserver(Observer o) { observers.remove(o); }

    public void notifyObservers() {
        for (Observer o : observers) {
            o.update(temperature);
        }
    }
}

Màn hình hiển thị:

javaCopyEditclass Display implements Observer {
    public void update(double temp) {
        System.out.println("Nhiệt độ mới: " + temp + "°C");
    }
}

Sử dụng:

javaCopyEditTemperatureSensor sensor = new TemperatureSensor();
Display screen = new Display();
sensor.addObserver(screen);

sensor.setTemperature(30.5); // In: Nhiệt độ mới: 30.5°C

✅ 16.5. Lợi ích của Observer Pattern

Lợi íchGiải thích
Tách biệt giữa phát & nhận thông tinKhông cần biết ai đang nghe
Dễ mở rộngThêm/xoá listener không ảnh hưởng Subject
Dễ test, module hoáMỗi Observer hoạt động độc lập
Giao tiếp 1 → N tự nhiênKhông cần lặp code notify thủ công

⚠️ 16.6. Nhược điểm & lưu ý

Nhược điểmCảnh báo
Không kiểm soát thứ tự gọi ObserverCó thể cần dùng queue nếu thứ tự quan trọng
Quá nhiều Observer → hiệu suất giảmDùng batch, thread-safe
Vòng lặp vô hạn nếu notify lẫn nhauCẩn thận với Observer gọi lại Subject

📦 16.7. Ứng dụng thực tế

Tình huốngCách áp dụng
UI thay đổi theo model (MVC)View là Observer, Model là Subject
Event System trong gameEnemy → Player → UI phản ứng
Đa màn hình dashboardCập nhật dữ liệu từ 1 nguồn trung tâm
WebSocket, Redis pub/sub, Kafka…Toàn bộ dùng pattern Observer!

💬 Ghi nhớ chương này:

  • Observer = “lắng nghe không đồng bộ”
  • Cần khi:
    • Một thay đổi → nhiều hệ quả
    • Bạn muốn decouple giữa data & hiển thị
    • Bạn cần hệ thống thông báo real-time

📡 Observer Pattern là cốt lõi của sự kiện, MVC, và hệ thống phân tán.

Strategy Pattern – Hoán đổi thuật toán linh hoạt

🎯 17.1. Vấn đề thực tế: nhiều thuật toán, không muốn if-else

Giả sử bạn có một ứng dụng:

  • Tính phí giao hàng: theo khoảng cách, khối lượng, thời gian
  • Mỗi trường hợp cần thuật toán khác nhau

Cách tệ nhất là:

javaCopyEditif (type == "FAST") { ... }
else if (type == "STANDARD") { ... }
else if (type == "CHEAP") { ... }

→ ❌ Khó mở rộng
→ ❌ Vi phạm nguyên lý Open/Closed
→ ❌ Rối rắm nếu có 10 loại thuật toán


✅ 17.2. Mục tiêu của Strategy Pattern

Cho phép bạn đóng gói các thuật toán thành các class riêng biệt, và hoán đổi chúng linh hoạt trong runtime.

  • Client không cần biết thuật toán bên trong
  • Chỉ cần “giao diện tính toán” + inject thuật toán phù hợp

🧩 17.3. Cấu trúc tổng quát

Thành phầnVai trò
Strategy (interface)Giao diện cho các thuật toán
ConcreteStrategyCác thuật toán cụ thể
ContextClass chính dùng Strategy
ClientGán strategy phù hợp cho context

✍️ 17.4. Ví dụ Java – Tính phí vận chuyển

Strategy:

javaCopyEditinterface ShippingStrategy {
    double calculateFee(double weight);
}

Các thuật toán cụ thể:

javaCopyEditclass FastShipping implements ShippingStrategy {
    public double calculateFee(double weight) {
        return 10 + weight * 1.5;
    }
}

class StandardShipping implements ShippingStrategy {
    public double calculateFee(double weight) {
        return 5 + weight * 1.0;
    }
}

Context:

javaCopyEditclass ShippingCalculator {
    private ShippingStrategy strategy;

    public void setStrategy(ShippingStrategy s) {
        this.strategy = s;
    }

    public double calculate(double weight) {
        return strategy.calculateFee(weight);
    }
}

Sử dụng:

javaCopyEditShippingCalculator calc = new ShippingCalculator();
calc.setStrategy(new FastShipping());
System.out.println(calc.calculate(2)); // 13.0

calc.setStrategy(new StandardShipping());
System.out.println(calc.calculate(2)); // 7.0

✅ 17.5. Lợi ích của Strategy Pattern

Lợi íchGiải thích
Không cần if-else chọn thuật toánDễ thêm loại mới
Mỗi thuật toán nằm riêng một classTăng tính module
Hoán đổi runtime dễ dàngDùng được với UI, cấu hình người dùng
Dễ test riêng từng strategyUnit test độc lập

⚠️ 17.6. Cảnh báo khi dùng Strategy

Vấn đềGợi ý
Quá nhiều class nhỏ lẻGom chung nếu đơn giản
Client truyền nhầm strategyCân nhắc dùng Factory kèm Strategy
Không cần thiết nếu chỉ có 1–2 thuật toánDùng kế thừa hoặc lambda

📚 17.7. Tình huống thực tế

Tình huốngÁp dụng Strategy để…
Tính khuyến mãi trong giỏ hàngMỗi loại giảm giá là 1 strategy
Sắp xếp dữ liệu trong bảngSort by Name, Price, Date…
Tính điểm bài thiTheo từng loại học sinh/đề thi
Game: hành vi tấn công/quái vậtMỗi loại AI là 1 strategy
Máy học: chọn thuật toán dự đoánTách model thành strategy

🔁 17.8. So sánh Strategy vs Template Method

Tiêu chíStrategyTemplate Method
Cách chọn hành viTruyền vào object khác (composition)Ghi đè method trong lớp con (inheritance)
Runtime hay compileRuntimeCompile-time
Thêm logic mớiDễ (thêm class strategy)Cần subclass

💬 Ghi nhớ chương này:

  • Strategy Pattern giúp bạn gỡ bỏ đống if-else chọn thuật toán
  • Phù hợp khi:
    • Có nhiều thuật toán cùng chức năng
    • Muốn chọn theo user/config/runtime
    • Muốn kiểm thử từng thuật toán riêng

🔧 “Cắm” thuật toán khác nhau mà không cần sửa logic gọi – chính là tinh thần Strategy.

Command Pattern

Biến hành động thành đối tượng, mẫu cực kỳ quan trọng trong nhóm Behavioral Patterns.

🎯 18.1. Vấn đề: Không muốn gọi phương thức trực tiếp

Giả sử bạn xây dựng hệ thống:

  • Giao diện có nhiều nút: Undo, Redo, Save, Load
  • Mỗi nút thực hiện một hành động khác nhau
  • Bạn muốn:
    ✅ Gán hành động động tại runtime
    ✅ Hủy (undo) hành động
    ✅ Ghi log, xếp hàng hành động

→ Việc gọi trực tiếp object.method() sẽ không đủ linh hoạt và khó điều khiển.


✅ 18.2. Mục tiêu của Command Pattern

Biến một hành động (ví dụ: gọi save()) thành một đối tượng riêng biệt, giúp bạn:

  • Trì hoãn, log, queue, undo hành động
  • Gán lại hành vi cho nút, phím tắt, hoặc hệ thống UI khác

🧱 18.3. Cấu trúc tổng quát

Thành phầnVai trò
Command (interface)Giao diện chung với hàm execute()
ConcreteCommandThực thi logic cụ thể
ReceiverĐối tượng thật thực hiện hành động
InvokerGọi lệnh (UI, controller, macro)
ClientTạo Command và gán cho Invoker

✍️ 18.4. Ví dụ Java – Remote điều khiển đèn

Interface:

javaCopyEditinterface Command {
    void execute();
}

Receiver:

javaCopyEditclass Light {
    public void on() {
        System.out.println("Đèn bật");
    }

    public void off() {
        System.out.println("Đèn tắt");
    }
}

ConcreteCommand:

javaCopyEditclass LightOnCommand implements Command {
    private Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    public void execute() {
        light.on();
    }
}

Invoker (remote control):

javaCopyEditclass RemoteControl {
    private Command slot;

    public void setCommand(Command command) {
        this.slot = command;
    }

    public void pressButton() {
        slot.execute();
    }
}

Sử dụng:

javaCopyEditLight light = new Light();
Command lightOn = new LightOnCommand(light);
RemoteControl remote = new RemoteControl();

remote.setCommand(lightOn);
remote.pressButton(); // Output: Đèn bật

✅ 18.5. Lợi ích của Command Pattern

Lợi íchGiải thích
Gán lại hành động cho UI linh hoạtKhông gắn cứng vào hàm cụ thể
Trì hoãn hoặc queue hành độngDùng cho undo/redo, xử lý đa luồng
Ghi log hành động dễ dàngMỗi hành động là 1 object riêng
Tuân thủ nguyên lý Single ResponsibilityTách UI, logic gọi và xử lý ra riêng

⚠️ 18.6. Cảnh báo khi dùng

Hạn chếLưu ý
Nhiều class nhỏ (mỗi command 1 class)Có thể dùng lambda (Java 8+), anonymous class
Khó trace lỗi nếu lệnh bị queue nhiềuCần log/trace rõ
Dễ rối nếu gộp command với ReceiverTách riêng trách nhiệm cho dễ test, bảo trì

📚 18.7. Tình huống sử dụng thực tế

Tình huốngCommand giúp gì?
Nút Undo/Redo trong editorGhi từng hành động làm Command, lưu lại stack
Giao diện nút bấm, menu độngDễ gán/hoán đổi hành vi
Macro trong gameGhi chuỗi lệnh để phát lại
Quản lý hàng đợi xử lý emailCommand queue
Lập lịch tác vụ (scheduler)Command + delay

🔁 18.8. So sánh Strategy vs Command

Tiêu chíStrategyCommand
Mục tiêu chínhHoán đổi thuật toánBiến hành động thành object
Có lưu được state?❌ Không✅ Có
Có undo được không?❌ (khó)✅ (rất tốt)
Thường dùng với UI?❌✅ (nút, phím tắt…)

💬 Ghi nhớ chương này:

  • Command Pattern giúp bạn kiểm soát hành động như một “đơn vị độc lập”
  • Phù hợp khi:
    • Cần undo, redo
    • Giao diện nhiều nút, keybinding
    • Hành động cần log, queue hoặc lưu trữ lại
  • Có thể kết hợp với:
    • Memento để undo
    • Macro để ghi hàng loạt hành động

🧠 Bạn không chỉ gọi method – bạn biến nó thành object để điều khiển như bạn muốn.

Template Method Pattern

Khung xử lý chung, tuỳ chỉnh chi tiết bằng kế thừa

🎯 19.1. Vấn đề: Khi quy trình tổng thể giống nhau, nhưng chi tiết mỗi bước khác nhau

Ví dụ: hệ thống xuất báo cáo PDF và báo cáo Excel đều trải qua quy trình:

  1. Chuẩn bị dữ liệu
  2. Xử lý logic báo cáo
  3. Định dạng
  4. Xuất file

→ Các bước giống nhau, nhưng:

  • Bước 3 và 4 khác nhau tuỳ loại file
  • Bạn muốn giữ khung cố định, chỉ cho phép override phần cần tùy chỉnh

✅ 19.2. Mục tiêu của Template Method Pattern

Cho phép định nghĩa khung xử lý chung (template) trong lớp cha, và cho phép lớp con override một phần các bước để thay đổi hành vi cụ thể.

→ Lớp con không thể thay đổi toàn bộ quy trình, chỉ được can thiệp vào những điểm được phép.


🧱 19.3. Cấu trúc tổng quát

Thành phầnVai trò
AbstractClassĐịnh nghĩa template method (final), và các bước (step1(), step2()) có thể abstract hoặc default
ConcreteClassGhi đè các bước cụ thể
ClientGọi đến templateMethod()

✍️ 19.4. Ví dụ Java – Xuất báo cáo

Lớp cha:

javaCopyEditabstract class ReportGenerator {
    public final void generateReport() {
        fetchData();
        processData();
        formatReport();
        export();
    }

    protected void fetchData() {
        System.out.println("Lấy dữ liệu từ DB");
    }

    protected abstract void processData();
    protected abstract void formatReport();
    protected abstract void export();
}

Lớp con: PDF

javaCopyEditclass PDFReport extends ReportGenerator {
    protected void processData() {
        System.out.println("Xử lý dữ liệu cho PDF");
    }

    protected void formatReport() {
        System.out.println("Định dạng PDF");
    }

    protected void export() {
        System.out.println("Xuất file PDF");
    }
}

Lớp con: Excel

javaCopyEditclass ExcelReport extends ReportGenerator {
    protected void processData() {
        System.out.println("Xử lý dữ liệu cho Excel");
    }

    protected void formatReport() {
        System.out.println("Định dạng bảng Excel");
    }

    protected void export() {
        System.out.println("Xuất file .xlsx");
    }
}

Sử dụng:

javaCopyEditReportGenerator report = new PDFReport();
report.generateReport();

✅ 19.5. Lợi ích của Template Method

Lợi íchGiải thích
Đảm bảo quy trình nhất quánVì khung được cố định trong lớp cha
Cho phép tuỳ chỉnh có kiểm soátLớp con chỉ override phần được phép
Tăng khả năng tái sử dụngKhung dùng lại nhiều lần
Dễ mở rộngChỉ cần thêm lớp con là có chức năng mới

⚠️ 19.6. Nhược điểm & lưu ý

Hạn chếGợi ý
Ràng buộc theo kế thừaKhông linh hoạt như Strategy (dùng composition)
Có thể dẫn đến “God class” nếu quá nhiều bướcCần chia nhỏ quy trình hợp lý
Không hỗ trợ runtime thay đổiChỉ chọn được hành vi tại compile-time

🔁 19.7. So sánh Template Method vs Strategy

Tiêu chíTemplate MethodStrategy
Kiểu mở rộngKế thừaComposition (thành phần)
Runtime chọn hành vi❌ Không✅ Có
Linh hoạt hơn❌✅
Kiểm soát chặt chẽ✅ Quy trình khó bị phá vỡ❌ Dễ bị thay đổi không kiểm soát

📚 19.8. Tình huống thực tế

Tình huốngMẫu này áp dụng tốt
Xuất nhiều loại báo cáo✅ Template Method
Gửi email với các định dạng khác nhau✅ Template Method
Chuỗi bước xử lý cố định trong game✅ Template Method
Xử lý request trong middleware✅ (Spring Web, Laravel middleware…)

💬 Ghi nhớ chương này:

  • Template Method = khung xử lý cố định + điểm mở cho mở rộng
  • Lớp con có thể:
    • Ghi đè bước cụ thể
    • Không phá vỡ cấu trúc tổng thể
  • Dễ tổ chức quy trình xử lý có cấu trúc

🧱 Nếu bạn có quy trình “bất biến trong tổng thể – biến động trong chi tiết”, hãy dùng Template Method.

State Pattern

Khi trạng thái thay đổi, hành vi cũng thay đổi

🎯 20.1. Vấn đề thực tế: hành vi thay đổi theo trạng thái

Ví dụ:

  • Máy ATM:
    • Khi đang chờ thẻ → chỉ chấp nhận insertCard()
    • Khi đã có thẻ → chấp nhận enterPin()
    • Khi rút tiền → không cho nạp thẻ nữa

Nếu xử lý bằng if...else theo trạng thái:

javaCopyEditif (state == WAITING_CARD) { ... }
else if (state == PIN_ENTERED) { ... }
else if (state == WITHDRAWING) { ... }

→ ❌ Rối rắm, khó mở rộng
→ ❌ Vi phạm nguyên lý đóng/mở


✅ 20.2. Mục tiêu của State Pattern

Cho phép một object thay đổi hành vi tương ứng với trạng thái nội tại, bằng cách ủy quyền hành vi cho các lớp trạng thái cụ thể.

  • Không cần if/else dài dòng
  • Mỗi trạng thái là một class riêng
  • Object “hoạt động như thể nó thay đổi class”

🧩 20.3. Cấu trúc tổng quát

Thành phầnVai trò
ContextĐối tượng chính, có trạng thái nội tại (hiện tại)
State (interface)Giao diện cho các hành vi cần override
ConcreteStateMỗi trạng thái cụ thể, triển khai logic riêng
ClientTương tác với Context – không cần biết state cụ thể

✍️ 20.4. Ví dụ Java – Máy ATM

Giao diện trạng thái:

javaCopyEditinterface ATMState {
    void insertCard();
    void enterPin();
    void withdraw();
}

Context:

javaCopyEditclass ATMContext {
    private ATMState currentState;

    public void setState(ATMState state) {
        this.currentState = state;
    }

    public void insertCard() {
        currentState.insertCard();
    }

    public void enterPin() {
        currentState.enterPin();
    }

    public void withdraw() {
        currentState.withdraw();
    }
}

Một trạng thái cụ thể:

javaCopyEditclass NoCardState implements ATMState {
    private ATMContext atm;

    public NoCardState(ATMContext atm) {
        this.atm = atm;
    }

    public void insertCard() {
        System.out.println("Thẻ được chèn vào.");
        atm.setState(new HasCardState(atm));
    }

    public void enterPin() {
        System.out.println("Chưa có thẻ.");
    }

    public void withdraw() {
        System.out.println("Chưa có thẻ.");
    }
}

→ Tương tự với HasCardState, AuthenticatedState, v.v.


✅ 20.5. Lợi ích của State Pattern

Lợi íchGiải thích
Loại bỏ if/else phức tạpMỗi trạng thái tự định nghĩa hành vi
Dễ thêm trạng thái mớiThêm class, không chạm logic cũ
Tách biệt logic theo vai tròMỗi class xử lý 1 trạng thái duy nhất
Runtime thay đổi hành vi linh hoạtChỉ cần gọi context.setState()

⚠️ 20.6. Nhược điểm & lưu ý

Hạn chếGợi ý
Nhiều class nhỏ lẻGom các state đơn giản thành enum-strategy nếu được
Giao tiếp giữa state và context phức tạpDùng truyền Context vào constructor
Trạng thái thay đổi không rõ ràngNên log lại mỗi lần đổi trạng thái

📚 20.7. Tình huống sử dụng thực tế

Tình huốngState pattern giúp gì?
Game: nhân vật ở trạng thái khác nhauAttack(), jump() tùy theo trạng thái
Đơn hàng: pending, shipped, deliveredMỗi trạng thái xử lý logic riêng
Cổng thanh toán: init, verified, failedMỗi trạng thái kiểm soát flow khác nhau
Giao diện đa bước (form wizard)Từng bước là 1 state

🔁 20.8. So sánh State vs Strategy

Tiêu chíState PatternStrategy Pattern
Dựa trên trạng thái nội tại✅ Có❌ Không
Thay đổi hành vi runtime✅✅
Dùng cho điều kiện nội bộ✅❌ (thường do người dùng chọn)
Giao diện người dùng thích hợpGame, máy trạng tháiBộ lọc, thuật toán lựa chọn

💬 Ghi nhớ chương này:

  • State Pattern giúp object:
    • Tự điều chỉnh hành vi mà không cần if/else
    • Chuyển đổi giữa các trạng thái một cách rõ ràng
  • Phù hợp với:
    • Game
    • Order flow
    • ATM, UI Wizard
    • Quy trình có trạng thái thay đổi tuần tự

🔄 Nếu “một object nhiều khuôn mặt” tùy thời điểm – hãy dùng State Pattern.

Chain of Responsibility Pattern

Xử lý theo chuỗi, đến khi có người nhận.

🎯 21.1. Vấn đề: nhiều handler, nhưng không biết ai xử lý

Bạn có hệ thống:

  • Middleware kiểm tra request: xác thực, phân quyền, ghi log…
  • Bộ phận tiếp nhận khiếu nại: nhân viên thường → trưởng phòng → giám đốc
  • Trình duyệt sự kiện: ai đó nhận, nếu không thì chuyển tiếp

Nếu dùng if...else:

javaCopyEditif (isAdmin(user)) { ... }
else if (isManager(user)) { ... }
else if (isSupport(user)) { ... }

→ ❌ Cứng nhắc
→ ❌ Không mở rộng được
→ ❌ Không thay đổi thứ tự xử lý dễ dàng


✅ 21.2. Mục tiêu của Chain of Responsibility Pattern

Cho phép bạn truyền yêu cầu qua một chuỗi các handler. Mỗi handler quyết định xử lý hoặc chuyển tiếp yêu cầu đến handler tiếp theo trong chuỗi.


🧱 21.3. Cấu trúc tổng quát

Thành phầnVai trò
Handler (interface)Khai báo hàm setNext() và handle()
ConcreteHandlerXử lý yêu cầu nếu phù hợp, nếu không → chuyển tiếp
ClientGửi yêu cầu vào đầu chuỗi

✍️ 21.4. Ví dụ Java – Xử lý đơn nghỉ phép

Giao diện handler:

javaCopyEditinterface LeaveApprover {
    void setNext(LeaveApprover approver);
    void processLeave(int days);
}

Các handler cụ thể:

javaCopyEditclass Manager implements LeaveApprover {
    private LeaveApprover next;

    public void setNext(LeaveApprover approver) {
        this.next = approver;
    }

    public void processLeave(int days) {
        if (days <= 2) {
            System.out.println("Manager duyệt nghỉ " + days + " ngày.");
        } else if (next != null) {
            next.processLeave(days);
        }
    }
}

class Director implements LeaveApprover {
    private LeaveApprover next;

    public void setNext(LeaveApprover approver) {
        this.next = approver;
    }

    public void processLeave(int days) {
        if (days <= 5) {
            System.out.println("Director duyệt nghỉ " + days + " ngày.");
        } else if (next != null) {
            next.processLeave(days);
        } else {
            System.out.println("Không ai có quyền duyệt nghỉ quá 5 ngày.");
        }
    }
}

Sử dụng:

javaCopyEditManager manager = new Manager();
Director director = new Director();

manager.setNext(director);

manager.processLeave(1); // Manager xử lý
manager.processLeave(4); // Director xử lý
manager.processLeave(10); // Không xử lý được

✅ 21.5. Lợi ích của Chain of Responsibility

Lợi íchGiải thích
Tăng tính mở rộngDễ thêm/xoá handler
Giảm phụ thuộc giữa các thành phầnMỗi handler không biết gì về trước đó
Linh hoạt thay đổi thứ tự xử lýDễ sắp xếp lại chuỗi
Phù hợp cho pipeline xử lýLogging, validation, security middleware…

⚠️ 21.6. Cảnh báo và nhược điểm

Hạn chếGợi ý
Yêu cầu không được xử lýCần handler cuối cùng hiển thị fallback
Debug khó nếu chuỗi dàiCần log từng bước trong chuỗi
Gắn chặt nếu setNext thủ côngDùng builder hoặc cấu hình động nếu chuỗi dài

📚 21.7. Ứng dụng thực tế

Tình huốngÁp dụng
Middleware HTTP (auth, CORS, log)Mỗi lớp là 1 handler
Giao diện sự kiện UIEvent handler truyền dần
Quy trình xét duyệtMỗi cấp quản lý là 1 handler
Chatbot trả lời theo từ khoáHandler theo priority
Validate form theo nhiều bướcChuỗi kiểm tra logic, ràng buộc

🔁 21.8. So sánh với các mẫu khác

So sánhChain of Responsibility
Xử lý 1 trong nhiều đối tượng✅
Cho phép mở rộng handler✅
Dựa theo chuỗi tuyến tính✅
So với StrategyStrategy chọn 1, Chain kiểm từng cái

💬 Ghi nhớ chương này:

  • Chain of Responsibility phù hợp khi:
    • Không biết ai sẽ xử lý yêu cầu
    • Muốn chia nhỏ trách nhiệm thành từng phần
    • Cần pipeline, middleware, hệ thống approval
  • Giúp tăng khả năng mở rộng và tách biệt logic xử lý

🔗 Bạn không cần biết ai sẽ giúp – cứ “đưa yêu cầu lên chuỗi”.

Memento Pattern

Khi bạn cần Undo, Save, và Rollback

🎯 22.1. Vấn đề: làm sao để “quay lại như chưa từng xảy ra”

Giả sử bạn xây dựng:

  • Ứng dụng soạn thảo văn bản
  • Trò chơi (game) cần lưu lại checkpoint
  • Hệ thống cấu hình cần lưu/khôi phục trạng thái trước đó

Bạn không thể expose toàn bộ nội dung bên trong object ra ngoài, nhưng vẫn cần:

✅ Lưu lại
✅ Khôi phục đúng trạng thái trước
✅ Không phá vỡ đóng gói (encapsulation)


✅ 22.2. Mục tiêu của Memento Pattern

Cho phép bạn lưu trạng thái nội tại của một object mà không để lộ chi tiết cấu trúc bên trong, và có thể khôi phục lại trạng thái đó sau này.


🧠 22.3. Cách hiểu đơn giản: “Bấm Ctrl + Z”

  • Bạn viết 1 dòng → hệ thống tự lưu trạng thái trước đó
  • Khi bấm “Undo”, trạng thái đó được phục hồi
  • Nhưng bên ngoài không thấy rõ hệ thống lưu như thế nào

→ Đó là Memento


🧱 22.4. Cấu trúc tổng quát

Thành phầnVai trò
OriginatorĐối tượng chứa trạng thái cần lưu
MementoGói dữ liệu trạng thái, không cho chỉnh sửa
CaretakerLưu trữ và phục hồi Memento

✍️ 22.5. Ví dụ Java – Trình soạn thảo đơn giản

Memento:

javaCopyEditclass EditorMemento {
    private final String content;

    public EditorMemento(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

Originator:

javaCopyEditclass Editor {
    private String content;

    public void write(String text) {
        content += text;
    }

    public EditorMemento save() {
        return new EditorMemento(content);
    }

    public void restore(EditorMemento memento) {
        this.content = memento.getContent();
    }

    public String getContent() {
        return content;
    }
}

Caretaker:

javaCopyEditclass History {
    private Stack<EditorMemento> history = new Stack<>();

    public void save(Editor editor) {
        history.push(editor.save());
    }

    public void undo(Editor editor) {
        if (!history.empty()) {
            editor.restore(history.pop());
        }
    }
}

Sử dụng:

javaCopyEditEditor editor = new Editor();
History history = new History();

editor.write("Hello ");
history.save(editor);

editor.write("World!");
System.out.println(editor.getContent()); // Hello World!

history.undo(editor);
System.out.println(editor.getContent()); // Hello 

✅ 22.6. Lợi ích của Memento Pattern

Lợi íchGiải thích
Hoàn tác (Undo) dễ dàngChỉ cần lưu & restore Memento
Giữ nguyên đóng gói (encapsulation)Không để lộ nội dung class ra ngoài
Dễ mở rộng cho phiên bản, checkpointDùng trong game, trình soạn thảo, cấu hình

⚠️ 22.7. Hạn chế và lưu ý

Hạn chếGiải pháp
Memento có thể nặng nếu object lớnNén/ghi log vi sai (diff)
Lưu trữ nhiều version tiêu tốn RAMDùng giới hạn số phiên bản
Phải cẩn thận khi restore sai thời điểmQuản lý bằng timestamp hoặc id

📚 22.8. Ứng dụng thực tế

Tình huốngỨng dụng
Soạn thảo văn bản (MS Word, Google Docs)Undo/Redo
Game – checkpoint, load saveLưu trạng thái level, HP, XP…
Giao diện nhập liệu nhiều bướcQuay lại bước trước
Hệ thống cấu hình nâng caoRollback config
Biểu đồ thay đổi dữ liệu theo thời gianLưu từng snapshot bằng Memento

🔁 22.9. So sánh với các mẫu khác

MẫuKhác biệt chính
CommandBiểu diễn hành động
MementoBiểu diễn trạng thái
StateThay đổi hành vi theo trạng thái nội tại
Memento + Command= Undo có hành động + dữ liệu

💬 Ghi nhớ chương này:

  • Memento = “đóng gói trạng thái, khôi phục linh hoạt”
  • Dùng khi:
    • Cần Undo
    • Cần lưu lại trạng thái hệ thống
    • Không muốn tiết lộ nội bộ object
  • Phù hợp với:
    • Text editor
    • Game checkpoint
    • Cấu hình đa phiên bản

🧳 Khi bạn cần “quay ngược thời gian” cho object – hãy dùng Memento.

Visitor Pattern

Khi bạn cần thêm hành vi mà không được sửa class.

🎯 23.1. Vấn đề: Không thể hoặc không nên sửa class gốc

Bạn có hệ thống:

  • Danh sách Employee, Manager, CEO
  • Bạn muốn in lương, tính thuế, xuất PDF…

→ Các hành vi đó không nên nhét hết vào class Employee

Nhưng nếu bạn không được sửa class (class từ thư viện, hệ thống cũ…),
hoặc không muốn phá vỡ nguyên tắc Single Responsibility?

→ Visitor Pattern là giải pháp.


✅ 23.2. Mục tiêu của Visitor Pattern

Cho phép bạn tách riêng các hành vi khỏi các đối tượng cần xử lý
→ bằng cách truyền “visitor” vào object để thực hiện hành vi.

  • Giữ object gọn nhẹ
  • Không phá vỡ đóng gói
  • Dễ thêm logic xử lý mới mà không sửa object

🧱 23.3. Cấu trúc tổng quát

Thành phầnVai trò
Element (interface)Giao diện object có thể “được thăm”
ConcreteElementLớp thật – chỉ cần triển khai accept(visitor)
Visitor (interface)Giao diện của các hành vi
ConcreteVisitorMỗi visitor là một loại xử lý

✍️ 23.4. Ví dụ Java – Nhân sự và tính thuế

Interface Employee:

javaCopyEditinterface Employee {
    void accept(Visitor visitor);
}

Các class:

javaCopyEditclass Developer implements Employee {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    public double getSalary() { return 1000; }
}

class Manager implements Employee {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    public double getSalary() { return 2000; }
}

Visitor:

javaCopyEditinterface Visitor {
    void visit(Developer d);
    void visit(Manager m);
}

Tính thuế:

javaCopyEditclass TaxCalculator implements Visitor {
    public void visit(Developer d) {
        System.out.println("Thuế Dev: " + d.getSalary() * 0.1);
    }

    public void visit(Manager m) {
        System.out.println("Thuế Manager: " + m.getSalary() * 0.2);
    }
}

Sử dụng:

javaCopyEditList<Employee> staff = List.of(new Developer(), new Manager());
Visitor tax = new TaxCalculator();

for (Employee e : staff) {
    e.accept(tax);
}

✅ 23.5. Lợi ích của Visitor Pattern

Lợi íchGiải thích
Thêm hành vi mới mà không sửa classChỉ cần tạo visitor mới
Áp dụng đa hình cho hành viPhân loại xử lý theo từng object cụ thể
Duy trì nguyên tắc Single ResponsibilityObject chỉ giữ dữ liệu, không xử lý

⚠️ 23.6. Nhược điểm và lưu ý

Hạn chếLưu ý
Mỗi Visitor phải biết mọi loại classMỗi visit(X) phải viết riêng
Dễ gây rối nếu object thay đổi liên tụcPhù hợp với hệ thống ổn định
Không phù hợp nếu object hierarchy thường xuyên đổiVisitor không linh hoạt như Strategy

📚 23.7. Ứng dụng thực tế

Tình huốngÁp dụng Visitor
In thông tin nhiều kiểu đối tượngVisitor = Printer
Tính thuế, báo cáo, thống kêVisitor = TaxCalculator, ReportGenerator
Duyệt cây AST (compiler)Visitor = Evaluator, Translator
Quét các phần tử trong DOM/XMLVisitor = NodeScanner

🔁 23.8. So sánh Visitor vs Strategy vs Command

MẫuDùng khi
StrategyChọn thuật toán khác nhau cho 1 object
CommandBiểu diễn hành động có thể delay, queue
VisitorThêm hành vi mới cho nhiều object khác nhau mà không sửa object

💬 Ghi nhớ chương này:

  • Visitor = đưa hành vi vào class từ bên ngoài
  • Phù hợp khi:
    • Object không nên hoặc không được sửa
    • Muốn thêm nhiều logic xử lý riêng cho object
    • Hệ thống ổn định về cấu trúc class

🧳 “Đừng nhét mọi thứ vào object – hãy để visitor đến làm thay.”

Mediator Pattern

Tập trung điều phối thay vì quan hệ chằng chịt

🎯 24.1. Vấn đề: Quá nhiều đối tượng gọi lẫn nhau gây rối

Giả sử bạn có form gồm nhiều component:

  • TextBox, Checkbox, Button, Label
  • Khi nhập Text → bật nút Submit
  • Khi click Checkbox → đổi nội dung Label
  • Khi click Submit → lock hết các ô nhập

→ Nếu các component gọi trực tiếp lẫn nhau:

javaCopyEdittextBox.onChange() → button.enable()  
checkbox.onClick() → label.setText()
...

→ ❌ Gây phụ thuộc chéo,
→ ❌ Khó test, khó mở rộng, dễ lỗi dây chuyền


✅ 24.2. Mục tiêu của Mediator Pattern

Giảm mối quan hệ trực tiếp giữa các đối tượng bằng cách đưa việc giao tiếp qua một “điều phối viên” trung tâm (mediator).

  • Các object không gọi nhau
  • Chỉ giao tiếp với Mediator
  • Mediator quyết định: ai sẽ phản ứng, phản ứng thế nào

🧱 24.3. Cấu trúc tổng quát

Thành phầnVai trò
Mediator (interface)Định nghĩa phương thức giao tiếp
ConcreteMediatorĐiều phối giao tiếp giữa các component
Colleague (component)Các object liên quan, giao tiếp qua Mediator

✍️ 24.4. Ví dụ Java – Giao diện đơn giản

Mediator:

javaCopyEditinterface UIMediator {
    void notify(Component sender, String event);
}

Component cha:

javaCopyEditabstract class Component {
    protected UIMediator mediator;
    public Component(UIMediator mediator) {
        this.mediator = mediator;
    }
}

Component cụ thể:

javaCopyEditclass TextBox extends Component {
    public void inputText(String text) {
        System.out.println("User nhập: " + text);
        mediator.notify(this, "textEntered");
    }
}

class Button extends Component {
    public void setEnabled(boolean enabled) {
        System.out.println("Button " + (enabled ? "bật" : "tắt"));
    }
}

ConcreteMediator:

javaCopyEditclass FormMediator implements UIMediator {
    private TextBox textBox;
    private Button button;

    public void setComponents(TextBox t, Button b) {
        this.textBox = t;
        this.button = b;
    }

    public void notify(Component sender, String event) {
        if (sender == textBox && event.equals("textEntered")) {
            button.setEnabled(true);
        }
    }
}

Sử dụng:

javaCopyEditFormMediator mediator = new FormMediator();
TextBox text = new TextBox(mediator);
Button btn = new Button(mediator);

mediator.setComponents(text, btn);

text.inputText("Hello"); // Button bật

✅ 24.5. Lợi ích của Mediator Pattern

Lợi íchGiải thích
Giảm số mối quan hệ phụ thuộcObject không biết về nhau
Dễ bảo trì, dễ mở rộngThêm logic mới chỉ cần thay đổi Mediator
Tăng tính module hóaComponent có thể tái sử dụng
Tăng tính kiểm soát luồng giao tiếpDễ theo dõi sự kiện xảy ra khi nào

⚠️ 24.6. Hạn chế và lưu ý

Hạn chếGợi ý
Mediator có thể bị “phình to”Nếu xử lý quá nhiều logic – chia thành nhiều mediator nhỏ
Phức tạp nếu lạm dụngDùng khi số lượng object lớn và quan hệ chồng chéo

📚 24.7. Ứng dụng thực tế

Tình huốngÁp dụng Mediator
Giao diện GUI, Form, WizardCác control giao tiếp qua Mediator
Game: NPC giao tiếp, AI ra quyết địnhMediator tổ chức phản ứng của nhóm
Hệ thống event-driven (event bus)Mediator trung gian phát sự kiện
Chatroom: người dùng không gửi trực tiếpServer chat = Mediator

🔁 24.8. So sánh Mediator vs Observer

Tiêu chíMediatorObserver
Chủ động điều phối✅ Có❌ Không (thụ động lắng nghe)
Mối quan hệ 2 chiều✅ CóChủ yếu 1 chiều từ Subject → Observer
Quản lý nhiều logic phức hợp✅❌ Observer đơn giản hơn

💬 Ghi nhớ chương này:

  • Mediator là “bộ điều khiển trung tâm” giúp các thành phần không cần biết nhau vẫn phối hợp được
  • Phù hợp khi:
    • Số lượng class nhiều
    • Quan hệ chằng chịt
    • Hệ thống cần tách UI và xử lý logic rõ ràng

🧩 Khi hệ thống trở thành “mạng nhện” của lời gọi phương thức – Mediator là người gỡ rối.

Interpreter Pattern

Xây dựng bộ diễn giải cho ngôn ngữ đơn giản

🎯 25.1. Vấn đề: Cần phân tích và thực thi “ngôn ngữ riêng”

Bạn có yêu cầu:

  • Cho phép người dùng viết câu lệnh như: A AND (B OR C)
  • Viết công thức rút gọn: x + y * z
  • Duyệt cây biểu thức (AST) hoặc DSL nội bộ (MiniLang)

→ Bạn cần một cách để “diễn giải” những câu lệnh đó, và xử lý như logic thật sự


✅ 25.2. Mục tiêu của Interpreter Pattern

Cho phép bạn định nghĩa cú pháp và cách diễn giải một ngôn ngữ đơn giản, thường dùng cho:

  • Máy tính biểu thức
  • Rule engine
  • Command mini language
  • Cây toán học / logic

🧱 25.3. Cấu trúc tổng quát

Thành phầnVai trò
Expression (interface)Khai báo hàm interpret(Context)
TerminalExpressionThành phần cơ bản (số, biến, true/false…)
NonTerminalExpressionThành phần kết hợp (AND, OR, Add, Multiply…)
ContextDữ liệu/biến cần cho việc interpret

✍️ 25.4. Ví dụ Java – Biểu thức logic đơn giản

Giả sử người dùng nhập: A AND (B OR C)

Ta biểu diễn:

Interface:

javaCopyEditinterface Expression {
    boolean interpret(Map<String, Boolean> context);
}

Terminal:

javaCopyEditclass Variable implements Expression {
    private String name;
    public Variable(String name) { this.name = name; }

    public boolean interpret(Map<String, Boolean> context) {
        return context.get(name);
    }
}

Non-terminal:

javaCopyEditclass Or implements Expression {
    private Expression left, right;
    public Or(Expression l, Expression r) {
        left = l; right = r;
    }

    public boolean interpret(Map<String, Boolean> context) {
        return left.interpret(context) || right.interpret(context);
    }
}

class And implements Expression {
    private Expression left, right;
    public And(Expression l, Expression r) {
        left = l; right = r;
    }

    public boolean interpret(Map<String, Boolean> context) {
        return left.interpret(context) && right.interpret(context);
    }
}

Sử dụng:

javaCopyEditExpression A = new Variable("A");
Expression B = new Variable("B");
Expression C = new Variable("C");

Expression expression = new And(A, new Or(B, C));

Map<String, Boolean> context = Map.of(
    "A", true,
    "B", false,
    "C", true
);

System.out.println(expression.interpret(context)); // true

✅ 25.5. Lợi ích của Interpreter Pattern

Lợi íchGiải thích
Xây ngôn ngữ mini dễ dàngTách biệt syntax và logic
Tạo cấu trúc cây logic rõ ràngRất giống AST trong compiler
Dễ mở rộng thêm toán tử mớiChỉ cần thêm class mới implement Expression

⚠️ 25.6. Nhược điểm và cảnh báo

Hạn chếGiải pháp
Không phù hợp với ngôn ngữ phức tạpDùng parser thật (ANTLR, yacc…)
Cây lồng nhau nhiều → khó debugDùng biểu diễn hậu tố (postfix), caching
Thiếu tối ưu hiệu suấtCần compile hoặc optimize nếu dùng thực tế

📚 25.7. Ứng dụng thực tế

Tình huốngÁp dụng
Hệ thống filter sản phẩm (A AND B)Diễn giải logic
Rule Engine nhỏXử lý điều kiện động
Máy tính mini / công thức toán họcDùng cho học sinh, khoa học cơ bản
Trình thông dịch biểu thức cấu hìnhVí dụ: price > 100 and category = 'A'

🔁 25.8. So sánh với các mẫu khác

MẫuSo sánh
InterpreterTự xây cú pháp và cách hiểu cho DSL
VisitorThêm hành vi cho các node của cây
StrategyChọn thuật toán đã có
CommandBiểu diễn lệnh đơn, không có ngữ pháp

💬 Ghi nhớ chương này:

  • Interpreter giúp xử lý logic “dạng ngôn ngữ” bên trong ứng dụng
  • Phù hợp khi:
    • Có ngôn ngữ miền hẹp (mini DSL)
    • Cần tách dữ liệu – cú pháp – xử lý
  • Rất mạnh trong: parser đơn giản, rule engine, DSL nội bộ

🧠 Khi object của bạn cần hiểu và thực hiện “ngôn ngữ riêng” – hãy nghĩ đến Interpreter.

Bài viết liên quan:

Các yếu tố để thiết kế phần mềm hiệu quả
Một số mẫu thiết kế phổ biến
Phân tích và thiết kế động
Phân tích và Thiết kế Tĩnh
Phân tích & Thiết kế hướng đối tượng linh hoạt (Agile Object Orientation)
Thiết kế Lấy Người Dùng Làm Trung Tâm (User Centred Design)
Giới thiệu nội dung series phân tích thiết kế và triển khai phần mềm

THÊM BÌNH LUẬN Cancel reply

Dịch vụ thiết kế Wesbite

NỘI DUNG MỚI CẬP NHẬT

Các khái niệm nâng cao trong C#

Kiểu dữ liệu Generics và Iterators trong C# 

Các lớp trừu tượng và Giao diện

Kế thừa và Đa hình

Tìm Hiểu Ràng Buộc UNIQUE Trong MySQL

Giới thiệu

hocvietcode.com là website chia sẻ và cập nhật tin tức công nghệ, chia sẻ kiến thức, kỹ năng. Chúng tôi rất cảm ơn và mong muốn nhận được nhiều phản hồi để có thể phục vụ quý bạn đọc tốt hơn !

Liên hệ quảng cáo: [email protected]

Kết nối với HỌC VIẾT CODE

© hocvietcode.com - Tech888 Co .Ltd since 2019

Đăng nhập

Trở thành một phần của cộng đồng của chúng tôi!
Registration complete. Please check your email.
Đăng nhập bằng google
Đăng kýBạn quên mật khẩu?

Create an account

Welcome! Register for an account
The user name or email address is not correct.
Registration confirmation will be emailed to you.
Log in Lost your password?

Reset password

Recover your password
Password reset email has been sent.
The email could not be sent. Possible reason: your host may have disabled the mail function.
A password will be e-mailed to you.
Log in Register
×