Decorator
Nội dung bài · 5 mục
- 1.Khái niệm
- 2.Ví dụ
- 3.Thử ngay
- 4.Lỗi hay gặp
- 5.Tóm tắt
Cần ghi log mỗi lần đọc danh sách sản phẩm để xem vì sao API chậm. Sửa thẳng
DbProductStore thì việc log lẫn vào việc đọc database, và muốn
FakeProductStore có log lại phải sửa thêm class đó. Decorator thêm việc log
bằng một class bọc bên ngoài, không đụng tới class gốc.
Khái niệm
🎁 Decorator: class implement cùng interface với class gốc, giữ object gốc bên trong, và làm thêm việc trước hoặc sau khi chuyển lời gọi cho object đó.
Nơi gọi vẫn chỉ thấy IProductStore, không biết mình đang dùng bản gốc hay
bản đã bọc. Bọc nhiều lớp cũng được, mỗi lớp thêm một việc.
Ví dụ
IProductStore và Product giữ như bài Truy vấn với EF Core. Ví dụ dùng
MemoryProductStore thay cho DbProductStore để chạy được không cần
database:
IProductStore store =
new LoggingProductStore(new MemoryProductStore());
var products = await store.InStockAsync();
public class LoggingProductStore : IProductStore
{
private readonly IProductStore _inner;
public LoggingProductStore(IProductStore inner)
{
_inner = inner;
}
public async Task<List<Product>> InStockAsync()
{
Console.WriteLine("Bắt đầu đọc sản phẩm");
var result = await _inner.InStockAsync();
Console.WriteLine(
$"Xong, {result.Count} sản phẩm");
return result;
}
public Task<bool> SetStockAsync(int id, int stock)
{
return _inner.SetStockAsync(id, stock);
}
}
public class MemoryProductStore : IProductStore
{
public Task<List<Product>> InStockAsync()
{
var list = new List<Product>
{
new Product { Id = 1, Name = "Bút bi" }
};
return Task.FromResult(list);
}
public Task<bool> SetStockAsync(int id, int stock)
{
return Task.FromResult(true);
}
}
// Có sẵn trong ShopApi từ khoá ASP.NET Core
public interface IProductStore
{
Task<List<Product>> InStockAsync();
Task<bool> SetStockAsync(int id, int stock);
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int Stock { get; set; }
}LoggingProductStoreimplementIProductStorevà giữ_innercũng làIProductStore.InStockAsyncin log, gọi_inner, in log tiếp.SetStockAsyncchuyển thẳng cho_inner.- Bọc
DbProductStorehayFakeProductStoređều được, vì decorator chỉ biết interface. Nơi gọi không phải sửa.
Thử ngay
Sửa LoggingProductStore để nhận thêm một tên và in tên đó trong hai dòng
log, rồi bọc hai lớp:
IProductStore inner = new LoggingProductStore(
"B", new MemoryProductStore());
IProductStore store =
new LoggingProductStore("A", inner);
await store.InStockAsync();
public class LoggingProductStore : IProductStore
{
private readonly string _name;
private readonly IProductStore _inner;
public LoggingProductStore(
string name, IProductStore inner)
{
_name = name;
_inner = inner;
}
public async Task<List<Product>> InStockAsync()
{
Console.WriteLine(_name + ": bắt đầu");
var result = await _inner.InStockAsync();
Console.WriteLine(
$"{_name}: xong, {result.Count} sản phẩm");
return result;
}
public Task<bool> SetStockAsync(int id, int stock)
{
return _inner.SetStockAsync(id, stock);
}
}Đoán trước khi chạy: bốn dòng log của A và B in ra theo thứ tự nào?
Xem kết quả
A: bắt đầu
B: bắt đầu
B: xong, 1 sản phẩm
A: xong, 1 sản phẩmLời gọi đi từ lớp ngoài vào trong, kết quả đi từ trong ra ngoài.
Lỗi hay gặp
Kế thừa class gốc để thêm log. Class log khi đó chỉ gắn với đúng một class
gốc, muốn log FakeProductStore phải viết thêm class khác.
// SAI — gắn chặt với DbProductStore
public class LoggingDbProductStore : DbProductStore
{
public LoggingDbProductStore(ShopDbContext db)
: base(db)
{
}
}// ĐÚNG — bọc bất kỳ IProductStore nào
public class LoggingProductStore : IProductStore
{
private readonly IProductStore _inner;
public LoggingProductStore(IProductStore inner)
{
_inner = inner;
}
public Task<List<Product>> InStockAsync()
{
return _inner.InStockAsync();
}
public Task<bool> SetStockAsync(int id, int stock)
{
return _inner.SetStockAsync(id, stock);
}
}Đây là cách dùng composition thay cho kế thừa, như bài Composition của khoá OOP.
Tóm tắt
- Decorator bọc object gốc, cùng interface, thêm việc trước hoặc sau.
- Class gốc và nơi gọi đều không phải sửa.
- Bọc nhiều lớp: lời gọi đi từ ngoài vào, kết quả đi từ trong ra.
- Hay dùng cho log, cache, đo thời gian, kiểm tra quyền.
Bình luận
Đang tải bình luận…