[Mở rộng] Decorator Design Pattern và Cách cài đặt với dependency injection bằng .NET Core
Note - Tips - Trick - .Net


Danh sách bài học
[Mở rộng] Decorator Design Pattern và Cách cài đặt với dependency injection bằng .NET Core
Dẫn nhập
Trong bài này, chúng ta sẽ cùng tìm hiểu về Decorator Design Pattern và cách cài đặt với dependency injection bằng .NET Core.
Decorator design pattern thuộc vào các design pattern về cấu trúc. Nó được sử dụng trong hai tình huống:
- Khi chúng ta muốn thêm tính năng cho một đối tượng.
- Khi chúng ta muốn thay đổi hành vi của đối tượng mà không cần phải chỉnh sửa code.
Ở cả hai trường hợp, bằng cách đóng gói một đối tượng, chúng ta có một đối tượng mới có thể quản lý những hành vi của nó như ý ta muốn. Vì vậy, ta có thể ví von decorator design pattern này như một con búp bê matryoshka (Búp bê babushka (Búp bê lồng nhau, Búp bê làm tổ,...) là một loại búp bê đặc trưng của Nga). Sau khi quan sát sơ lược cấu trúc của decorator design pattern, chúng ta sẽ tiếp tục đi sâu vào phần ví dụ và mã lệnh cho hai tình huống trên.
Ghi chú: Mã lệnh mẫu được lưu trữ trong Github.
Những thành phần của Decorator design pattern
Trong đó:
- Component: đây là một bộ tài nguyên trong hệ thống, được sử dụng để thực hiện những nhiệm vụ cụ thể nhất định. Đây là thành phần được định nghĩa như một lớp trừu tượng hay một interface.
- Concrete component: đây là phần cài đặt của Component.
- Decorator: đây là phần tài nguyên kế thừa Component (có những thuộc tính được khai báo ở Component và một số thuộc tính được định nghĩa thêm). Decorator có thể là lớp trừu tượng hoặc interface như Component. Một đối tượng là Decorator cũng là một Component và nó có thể được định nghĩa là một Component. Hay nói cách khác, mối quan hệ giữa Decorator và Component vừa có thể là IS A, vừa có thể là HAS A.
- Concrete Decorator: đây là phần cài đặt của Decorator.
Làm thể nào để thêm một tính năng mới với Decorator Design Pattern?
Chúng ta cùng xét interface IProductRepository. Interface này có phương thức GetById làm nhiệm vụ thể hiện thông tin của sản phẩm thông qua Id. Tạo một lớp con của interface này tên DbProductRepository. Khi chúng ta tạo interface IproductRepository cùng những thuộc tính và phương thức của nó, chúng ta đang tạo Component và Concrete Component. Bên dưới là lược đồ UML và code của kiến trúc này.
public interface IProductRepository
{
Product? GetProductById(string id);
}
public class DbProductRepository : IProductRepository
{
private readonly ProductDbContext _productDbContext;
public DbProductRepository(ProductDbContext productDbContext)
{
_productDbContext = productDbContext;
}
public Product? GetProductById(string id)
{
return _productDbContext.Products.FirstOrDefault(x => x.Id == id);
}
}
Sau đó, hãy nói đến sự tồn tại của quản trị viên. Quản trị viên có thể đọc dữ liệu của sản phẩm như người dùng bình thường cũng như có thể thêm sản phẩm vào hệ thống. Khi chúng ta muốn thêm tính năng mới mà không làm ảnh hưởng đến hệ thống cũ, Decorator Design Pattern là giải pháp cho vấn đề này.
Bắt đầu với việc thiết kế một interface tên IadminProductRepository, chúng ta hoàn thiện mô hình Decorator Design Pattern.
public interface IAdminProductRepository : IProductRepository
{
string CreateProduct(Product product);
}
public class AdminProductRepository : IAdminProductRepository
{
private readonly IProductRepository _productRepository;
private readonly ProductDbContext _productDbContext;
public AdminProductRepository(IProductRepository productRepository, ProductDbContext productDbContext)
{
_productRepository = productRepository;
_productDbContext = productDbContext;
}
public Product? GetProductById(string id)
{
return _productRepository.GetProductById(id);
}
public string CreateProduct(Product product)
{
_productDbContext.Products.Add(product);
_productDbContext.SaveChanges();
return product.Id;
}
}
Chúng ta có thể quản lí các dependencies bằng cách sử dụng thư viện Microsoft Dependency Injection như bên dưới.
...
services.AddScoped<IProductRepository, DbProductRepository>();
services.AddScoped<IAdminProductRepository>(provider =>
new AdminProductRepository(provider.GetRequiredService<IProductRepository>(),
provider.GetRequiredService<ProductDbContext>()));
...
Trong khi hệ thống cũ vẫn có thể làm việc với interface IProductRepository, chúng ta vẫn có thể thêm tính năng mới bằng cách tạo một instance của IAdminProductRepository cho tính năng mới là tạo sản phẩm. Nhờ vào Decorator Design Pattern, chúng ta có thể phát triển phần mềm linh hoạt với nguyên tắc Đóng và Mở.
Làm thể nào để thay đổi hành vi của đối tượng với Decorator Design Pattern?
Nhìn vào đoạn code bên dưới, một câu truy vấn sản phẩm dựa theo Id được tạo ra bằng cách yêu cầu một đối tượng từ một IoC container kiểu IProductRepository. Vì đối tượng được chúng ta đưa vào hệ thống nên câu truy vấn này được tạo ra từ cơ sở dữ liệu.
...
services.AddScoped<IProductRepository, DbProductRepository>();
...
[Route("products")]
public class ProductController : ControllerBase
{
private readonly IProductRepository _productRepository;
public ProductController(IProductRepository productRepository)
{
_productRepository = productRepository;
}
[HttpGet("{id}")]
public IActionResult GetProductById([FromRoute] string id)
{
Product? product = _productRepository.GetProductById(id);
if (product == null) return StatusCode((int) HttpStatusCode.NotFound);
return StatusCode((int) HttpStatusCode.OK, product);
}
}
Giả định rằng hệ thống chúng ta cần hoạt động nhanh hơn, chúng ta cần cài đặt một cơ chế bộ đệm giúp việc truy vấn thông tin sản phẩm diễn ra nhanh hơn. Trong trường hợp này, khi cần truy vấn thông qua IProductRepository, chúng ta cần tìm kiếm trong bộ nhớ đệm trước. Nếu không tìm được phần dữ liệu phù hợp chúng ta mới yêu cầu tìm kiếm trong cơ sở dữ liệu. Chúng ta có thể thay đổi lớp DbProductRepository để thực hiện việc này. Chúng ta có thể thực hiện truy vấn trong bộ nhớ đệm trước khi truy cập vào cơ sở dữ liệu. Tuy nhiên, khi làm cách này, chúng ta sẽ phá vỡ nguyên lý Open – Closed vì khi đó chúng ta đã làm thay đổi code cũ trong dự án. Hơn nữa, chúng ta cũng đã phá vỡ nguyên lý Single – Responsibility vì chúng ta vừa quản lí cơ sở liệu, vừa quản lí bộ nhớ đệm, trong cùng một lớp. Decorator design pattern sẽ giúp chúng ta tuân theo nguyên tắc SOLID hơn.
Hãy tạo một lớp CacheProductRepository. Lớp này được kế thừa từ interface IProductRepository. Nó sẽ tìm kiếm trong bộ nhớ đệm trước, nếu không thể tìm ra kết quả, nó sẽ chuyển giao công việc tìm kiếm này cho một đối tượng IProductRepository khác. Nếu tìm được kết quả, nó sẽ được lưu vào bộ nhớ đệm cho các lần tìm kiếm tiếp theo.
public class CacheProductRepository : IProductRepository
{
private readonly IProductRepository _productRepository;
private readonly IMemoryCache _memoryCache;
private readonly TimeSpan _cacheTimeLimit = TimeSpan.FromSeconds(15);
public CacheProductRepository(IProductRepository productRepository, IMemoryCache memoryCache)
{
_productRepository = productRepository;
_memoryCache = memoryCache;
}
public Product? GetProductById(string id)
{
_memoryCache.TryGetValue(id, out Product? product);
if (product != null)
return product;
product = _productRepository.GetProductById(id);
if (product != null)
_memoryCache.Set(product.Id, product, _cacheTimeLimit);
return product;
}
// Imaginary Method
public void X()
{
_productRepository.X();
}
}
Sau khi thiết kế lớp này, chúng ta có thể chỉnh sửa các dependency của CompositionRoot như bên dưới.
// WAY 1 (with Scrutor -> https://github.com/khellang/Scrutor)
services.AddScoped<IProductRepository, DbProductRepository>();
services.Decorate<IProductRepository>((inner, provider) => new CacheProductRepository(inner, provider.GetRequiredService<IMemoryCache>()));
// WAY 2 (with Microsoft Dependency Injection)
services.AddScoped<DbProductRepository>();
services.AddScoped<IProductRepository, CacheProductRepository>(provider =>
new CacheProductRepository(provider.GetRequiredService<DbProductRepository>(),
provider.GetRequiredService<IMemoryCache>()));
Sau khi hệ thống nhận biết các dependency, chúng ta sẽ cài đặt các thay đổi chúng ta muốn về các hành vi của service nhận được thông qua IProductRepository.
Kết
Như vậy chúng ta đã tìm hiểu về Decorator Design Pattern và cách cài đặt với dependency injection bằng .NET Core
Đây là một phụ lục nhằm giúp các bạn bổ sung thêm kiến thức về chủ đề chính và không nằm trong chương trình của khóa học.
Cảm ơn các bạn đã theo dõi bài viết. Hãy để lại bình luận hoặc góp ý của mình để phát triển bài viết tốt hơn. Đừng quên “Luyện tập – Thử thách – Không ngại khó”.
Tải xuống
Tài liệu
Nhằm phục vụ mục đích học tập Offline của cộng đồng, Kteam hỗ trợ tính năng lưu trữ nội dung bài học [Mở rộng] Decorator Design Pattern và Cách cài đặt với dependency injection bằng .NET Core dưới dạng file PDF trong link bên dưới.
Ngoài ra, bạn cũng có thể tìm thấy các tài liệu được đóng góp từ cộng đồng ở mục TÀI LIỆU trên thư viện Howkteam.com
Đừng quên like và share để ủng hộ Kteam và tác giả nhé!

Thảo luận
Nếu bạn có bất kỳ khó khăn hay thắc mắc gì về khóa học, đừng ngần ngại đặt câu hỏi trong phần bên dưới hoặc trong mục HỎI & ĐÁP trên thư viện Howkteam.com để nhận được sự hỗ trợ từ cộng đồng.
Nội dung bài viết