Project: refactor SimUDuck bằng Strategy và Observer
Đề refactor đàn vịt kế thừa cứng nhắc sang Strategy và gắn bảng quan sát, kèm rubric chấm bốn mức.
KhóDesign pattern ~120 phút
Ôn lại trước khi học
Giới thiệu
Bài này refactor đàn vịt kế thừa cứng sang ủy thác. Dùng để đổi cánh tên lửa lúc chạy không vỡ code.
Kiến thức cần nhớ
Mùi code cũ
- Lớp cha ôm bay kêu bơi hiển thị tất cả.
- Vịt cao su kế thừa nên biết bay sai bét.
- Logic bay lặp lại ở nhiều lớp con khác.
- Ví dụ đời thường: như 1 remote điều khiển cả nhà sai.
- Thêm vịt mới là phải mở lớp cũ ra vá.
Strategy tách bay kêu
- Tách họ bay: cánh, không bay, tên lửa riêng.
- Tách họ kêu: quạc, chíp, im lặng riêng biệt.
- Vịt chỉ giữ 2 tham chiếu rồi ủy thác việc.
- Ví dụ đời thường: như thay pin thay lưỡi dao rời.
- Đổi hành vi lúc chạy mà vịt khác không ảnh hưởng.
| Việc | Code cũ | Code mới |
|---|---|---|
| Thêm vịt | Sửa lớp cha | Thêm lớp mới |
| Đổi bay | Ghi đè vá víu | Gán hành vi mới |
| Đếm kêu | Đếm tay thủ công | Observer báo tự động |
Observer đếm báo
- Mỗi vịt là 1 chủ đề nhỏ phát tin kêu.
- Bảng đếm và nhà nghiên cứu đăng ký nghe tin.
- Mỗi tiếng kêu là 1 tin báo tự động gửi.
- Ví dụ đời thường: như follow vịt là nhận tiếng kêu.
- Bảng mới chỉ cần đăng ký, không sửa vịt.
Ví dụ
- Input: vịt mồi đang chạy xin cánh tên lửa -> Output: chỉ nó bay đổi.
- Giải thích: ủy thác nên gán lại 1 tham chiếu là xong.
# Đổi hành vi lúc chạy không sửa lớp
vit_moi.kieu_bay = TenLua() # gán hành vi mới
vit_moi.bay() # bay tên lửa ngay
💻 Bài tập
Ngôn ngữ: C++17 (hoặc Java). Đây là project, dành 90 phút. Nộp vào day-116-simuduck/.
Bài 1: Code cũ cần refactor
Viết lại đúng phiên bản "trước refactor" trong before/:
class Duck {
public:
virtual std::string display() const = 0;
std::string quack() const { return "Quạc"; }
std::string fly() const { return "Bay bằng cánh"; }
std::string swim() const { return "Bơi"; }
};
class RubberDuck : public Duck { /* ghi đè quack bằng "Chít", fly... không ghi đè được vì không virtual */ };
class DecoyDuck : public Duck { ... };
Viết test before_test.cpp chỉ ra 3 lỗi: vịt cao su bay được, vịt mồi kêu được, muốn đổi hành vi lúc chạy thì không thể.
Bài 2: Refactor bằng Strategy và Observer
Thư mục after/:
| Yêu cầu | Chi tiết |
|---|---|
| Strategy | 2 họ FlyBehavior, QuackBehavior. Vịt giữ con trỏ, set... đổi lúc chạy |
| Observer | Mỗi vịt là subject. QuackCounterBoard đếm tổng tiếng kêu. ResearcherLog ghi "<tên>: <tiếng kêu>" |
| Không phá hành vi | Mọi hành vi đúng của before/ giữ nguyên |
| Mở rộng | Thêm RobotDuck (bay bằng tên lửa, kêu "Bíp") chỉ bằng file mới |
RubberDuck.performFly() -> "Không bay được"
DecoyDuck.setFlyBehavior(Rocket); performFly() -> "Bay bằng tên lửa"; các vịt khác không đổi
3 vịt kêu mỗi con 2 lần -> board đếm 6, log có 6 dòng
Bài 3: Báo cáo refactor (nâng cao)
Viết REFACTOR.md:
- Sơ đồ lớp trước và sau (Mermaid).
- Bảng: yêu cầu thay đổi, số file phải sửa ở
before/, số file phải sửa ởafter/. Ít nhất 4 yêu cầu (thêm vịt, thêm kiểu bay, đổi bay lúc chạy, thêm người quan sát). - Nguyên lý thiết kế đã áp dụng (đóng gói phần thay đổi, ưu tiên composition, lập trình theo interface) và chỗ áp dụng trong code.
✅ Tiêu chí hoàn thành
-
before_test.cppchỉ ra đủ 3 lỗi. - Test
after/pass,RobotDuckchỉ là file mới. -
REFACTOR.mdcó 2 sơ đồ và bảng 4 yêu cầu. - Commit tách rõ: code cũ, test lỗi, refactor Strategy, thêm Observer.
❓ Câu hỏi ôn tập
- Mùi lớn nhất của kế thừa là gì?
- Strategy tách cái gì ra?
- Ai nhận tin khi vịt kêu?
- Thêm bảng mới có sửa vịt không?
🌕 Bạn đã hoàn thành ngày 116. Làm hết bài tập phía trên rồi sang ngày tiếp theo.