Chain of Responsibility và Flyweight

Chuỗi xử lý yêu cầu mua thư viện email, và chia sẻ nét vẽ cây trong rừng ngàn cây của Flyweight.
KhóDesign pattern ~60 phút

Giới thiệu

Bài này học chuyền yêu cầu qua chuỗi và chia sẻ object. Dùng để duyệt mua nhiều cấp và vẽ rừng ngàn cây.

Kiến thức cần nhớ

Chain of Responsibility

  • Định nghĩa: chuyền yêu cầu dọc chuỗi tới khi có người xử lý.
  • Mỗi mắt xích tự xử hoặc chuyền cho người kế tiếp.
  • Ví dụ đời thường: như đơn xin duyệt qua từng cấp trên.
  • Người gửi chỉ đưa đầu chuỗi, không cần biết ai xử.
  • Thêm cấp mới chỉ cần chen 1 mắt xích vào.

Ví dụ chuỗi duyệt mua

  • Trưởng nhóm duyệt dưới 1 triệu.
  • Trưởng phòng duyệt dưới 10 triệu.
  • Giám đốc duyệt mọi mức còn lại.
  • Ví dụ đời thường: đơn 5 triệu dừng ở trưởng phòng.
  • Cần mắt chặn cuối để đơn không rơi mất.
Mắt xíchHạn mứcViệc làm
Trưởng nhómDưới 1 triệuTự duyệt hoặc chuyền
Trưởng phòngDưới 10 triệuTự duyệt hoặc chuyền
Giám đốcMọi mức còn lạiChặn cuối chuỗi

Flyweight

  • Định nghĩa: chia sẻ phần chung, tách riêng phần khác biệt.
  • Ngàn cây chung vài nét vẽ, khác vị trí và màu.
  • Ví dụ đời thường: như dùng chung khuôn in thay ngàn bản.
  • Cache phần chung nên RAM giảm mạnh.
  • Phần riêng lưu ngoài, truyền vào khi vẽ.

Ví dụ

  • Input: đơn mua 5 triệu -> Output: trưởng phòng duyệt.
  • Giải thích: trưởng nhóm chuyền, trưởng phòng đủ hạn mức xử lý.
# Mắt xích tự xử hoặc chuyền tiếp
def xu_ly(yeu_cau):
    if yeu_cau <= han_muc:  # đủ quyền thì xử
        return "duyệt"
    return nguoi_ke.xu_ly(yeu_cau)  # không thì chuyền

💻 Bài tập

Ngôn ngữ: C++17 (hoặc Java). Nộp vào day-104/.

Bài 1: Chuỗi duyệt chi tiêu

struct Request { std::string id; long long amount; std::string purpose; };
class Approver {
 protected: std::shared_ptr<Approver> next;
 public:
  std::shared_ptr<Approver> setNext(std::shared_ptr<Approver> n);   // trả n để nối chuỗi
  virtual std::string handle(const Request &r);                      // mặc định: chuyền tiếp
  virtual ~Approver() = default;
};
Mắt xíchDuyệt khi
TeamLeadamount < 1.000.000
Manageramount < 10.000.000
Directoramount < 100.000.000
Board (chặn cuối)Mọi mức còn lại, trả "cần họp HĐQT"
5.000.000   -> "Manager duyệt"
500.000     -> "TeamLead duyệt"
2 tỉ        -> "cần họp HĐQT"
Chuỗi không có Board, 2 tỉ -> "không ai xử lý" (không crash, không rơi mất)

Chen thêm FraudCheck ở đầu chuỗi: từ chối nếu purpose chứa từ khóa đen. Không sửa lớp nào khác.

Bài 2: Middleware HTTP

Viết chuỗi xử lý request giả lập: Logging -> Auth -> RateLimit -> Router.

  • Auth từ chối (401) nếu thiếu header Authorization.
  • RateLimit từ chối (429) nếu 1 IP quá 5 request/giây (dùng đồng hồ giả).
  • Logging ghi lại mọi request kể cả bị từ chối ở sau (cần xử lý khi chuỗi trả về).

Bài 3: Flyweight cho rừng cây (nâng cao)

struct TreeType { std::string name; std::string color; std::vector<uint8_t> texture; };  // texture 64 KB
struct Tree { int x, y; const TreeType *type; };
class TreeFactory { public: const TreeType *get(const std::string &name, const std::string &color); size_t typeCount() const; };
  1. Trồng 1.000.000 cây với 5 loại. In bộ nhớ ước lượng khi mỗi cây giữ texture riêng và khi dùng Flyweight.
  2. typeCount() phải bằng 5.
  3. Đo bộ nhớ thật bằng /proc/self/status (VmRSS) cho 2 cách.

✅ Tiêu chí hoàn thành

  • Test chuỗi duyệt pass, không yêu cầu nào rơi mất.
  • Chen FraudCheck không sửa lớp cũ.
  • Middleware ghi log cả request bị từ chối.
  • Bảng bộ nhớ Flyweight có số thật.

❓ Câu hỏi ôn tập

  • Ai quyết định chuyền trong chain?
  • Mắt chặn cuối để làm gì?
  • Flyweight chia sẻ cái gì?
  • Phần riêng lưu ở đâu?

🌕 Bạn đã hoàn thành ngày 104. Làm hết bài tập phía trên rồi sang ngày tiếp theo.

Bớt paste mù, hiểu sâu từng dòng mình viết.

180 Days Challenge. 180 ngày cai nghiện vibe code.