Decorator thực tế: thư viện vào-ra và tự viết decorator
Nhìn Decorator trong thư viện vào ra chuẩn, cách bọc luồng dữ liệu chồng nhiều tầng, và bài tập tự viết decorator chữ thường.
KhóDesign pattern ~60 phút
Ôn lại trước khi học
Giới thiệu
Bài này học Decorator trong thư viện đọc file. Bạn dùng nó để chồng tầng đệm và đếm dòng.
Kiến thức cần nhớ
Luồng chuẩn là Decorator
- Lõi là luồng đọc thô từ file hay bộ nhớ.
- Quanh lõi là các tầng: đệm, đếm dòng, đọc kiểu, nén.
- Mọi mắt xích đều là luồng nên bọc lẫn nhau được.
- Ví dụ đời thường: như ống nước nối thêm đầu lọc, lõi vẫn là ống.
Đường đi yêu cầu đọc
- Tầng ngoài nhận đọc 1 ký tự thì hỏi tầng trong.
- Tầng đệm còn hàng thì trả ngay, hết thì hỏi lõi file.
- Mỗi tầng chỉ làm đúng việc mình rồi ủy thác phần còn lại.
- Ví dụ đời thường: như hỏi kho, kho hết mới hỏi nhà máy.
| Khía cạnh | Cà phê Starbuzz | Luồng vào-ra |
|---|---|---|
| Lõi | Đồ uống nền | Luồng đọc thô |
| Lớp bọc | Sữa kem | Đệm đếm dòng nén |
| Thêm mới | Topping mới | Tầng đọc mới |
Thứ tự bọc
- Cà phê đổi thứ tự topping giá vẫn vậy.
- Luồng đổi thứ tự có thể sai: giải nén phải nằm trong đọc kiểu.
- Người chồng tầng phải hiểu ngữ nghĩa từng tầng.
- Ghi rõ thứ tự và lý do trong tài liệu.
Tự viết tầng chữ thường
- Lớp mới cũng là luồng nên dùng ở mọi nơi cần luồng.
- Giữ 1 luồng bên trong, đọc ký tự về rồi đổi hoa thành thường.
- Không sửa thư viện cũ, chỉ thêm mắt xích mới.
- Chồng nhiều tầng quá thì khó debug và khó đọc.
Ví dụ
- Input: đọc file qua tầng đệm. Output: đúng từng ký tự, ít lần chạm đĩa hơn.
- Giải thích: kho đệm trả ngay khi còn hàng.
- Input:
HeLLoqua tầng chữ thường. Output:hello. - Giải thích: chữ hoa bị đổi, chữ thường và ký tự khác giữ nguyên.
💻 Bài tập
Ngôn ngữ: C++17 (hoặc Java với FilterInputStream). Nộp vào day-071/.
Bài 1: Chuỗi luồng đọc
class InputStream {
public:
virtual int read() = 0; // trả byte 0..255, hết dữ liệu trả -1
virtual ~InputStream() = default;
};
class FilterInputStream : public InputStream {
protected: std::unique_ptr<InputStream> in;
public: explicit FilterInputStream(std::unique_ptr<InputStream> in);
int read() override { return in->read(); }
};
| Lớp | Việc phải làm |
|---|---|
FileInputStream(path) | Đọc thô, mỗi read() là 1 lần fread 1 byte. Đếm số lần gọi hệ thống |
MemoryInputStream(std::string) | Đọc từ bộ nhớ |
BufferedInputStream(in, size) | Đọc khối size byte vào đệm |
LowerCaseInputStream(in) | Đổi A-Z thành a-z |
LineCountingInputStream(in) | Đếm \n, có lineCount() |
LowerCase(Memory("HeLLo")) đọc hết -> "hello"
LineCounting(Memory("a\nb\nc")) đọc hết -> lineCount = 2
Buffered(File(1 MB), 4096) -> số lần gọi đọc file thô ~ 256 thay vì ~10^6
Bài 2: Thứ tự bọc có ý nghĩa
Thêm Rot13InputStream và Base64DecodeInputStream.
- Mã hóa chữ
"Xin Chao"bằng Rot13 rồi Base64, lưu vàosecret.txt. - Chồng tầng đọc đúng thứ tự để ra lại
"xin chao"(chữ thường). Chồng sai thứ tự và ghi lại kết quả sai. - Viết comment trong code: thứ tự đúng và lý do.
Bài 3: Luồng ghi và nén (nâng cao)
- Viết phía ghi tương ứng:
OutputStream,FileOutputStream,BufferedOutputStream(nhớflushtrong destructor),UpperCaseOutputStream. - Viết
RleOutputStreamvàRleInputStream: nén chạy dài dạng(số lần, byte). Kiểm tra ghi rồi đọc lại đúng. - Đo thời gian ghi 10 MB có và không có
BufferedOutputStream.
✅ Tiêu chí hoàn thành
- Test mẫu pass.
- Số lần gọi hệ thống giảm rõ nhờ tầng đệm (có số đo).
- Bài 2 ghi lại được kết quả sai khi đổi thứ tự.
- Không mất dữ liệu cuối file khi quên gọi
flush(destructor lo việc này).
❓ Câu hỏi ôn tập
- Lõi và tầng bọc trong luồng là gì?
- Yêu cầu đọc đi qua các tầng thế nào?
- Vì sao thứ tự bọc luồng quan trọng?
- Tầng chữ thường tự viết hoạt động ra sao?
🌕 Bạn đã hoàn thành ngày 71. Làm hết bài tập phía trên rồi sang ngày tiếp theo.