Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SOLID Principles

Giới thiệu

SOLID là tập hợp 5 nguyên tắc thiết kế hướng đối tượng giúp tạo ra code dễ bảo trì, mở rộng và tái sử dụng. Các nguyên tắc này được đặt ra bởi Robert C. Martin (Uncle Bob) và đã trở thành tiêu chuẩn trong ngành công nghiệp phần mềm.

1. Single Responsibility Principle (SRP)

Định nghĩa

“Một class chỉ nên có một lý do để thay đổi” - mỗi class chỉ nên đảm nhận một responsibility duy nhất.

Tại sao cần SRP?

  • Giảm sự phụ thuộc giữa các phần của hệ thống
  • Dễ dàng test và debug
  • Giảm thiểu tác động khi thay đổi requirement
  • Code dễ đọc và bảo trì hơn

Ví dụ vi phạm SRP

// ❌ VI PHẠM - Class có nhiều responsibilities
public class OrderProcessor
{
    public void ProcessOrder(Order order)
    {
        // 1. Validate order
        if (!ValidateOrder(order))
            throw new InvalidOrderException();
        
        // 2. Save to database
        SaveToDatabase(order);
        
        // 3. Send confirmation email
        SendEmail(order);
        
        // 4. Generate invoice
        GenerateInvoice(order);
        
        // 5. Update inventory
        UpdateInventory(order);
    }
    
    private bool ValidateOrder(Order order) { /* validation logic */ }
    private void SaveToDatabase(Order order) { /* database logic */ }
    private void SendEmail(Order order) { /* email logic */ }
    private void GenerateInvoice(Order order) { /* invoice logic */ }
    private void UpdateInventory(Order order) { /* inventory logic */ }
}

Ví dụ tuân thủ SRP

// ✅ TUÂN THỦ - Tách thành các classes chuyên biệt
public class OrderValidator
{
    public bool Validate(Order order) { /* validation logic */ }
}

public class OrderRepository
{
    public void Save(Order order) { /* database logic */ }
}

public class EmailService
{
    public void SendOrderConfirmation(Order order) { /* email logic */ }
}

public class InvoiceService
{
    public void Generate(Order order) { /* invoice logic */ }
}

public class InventoryService
{
    public void Update(Order order) { /* inventory logic */ }
}

// Class chính chỉ điều phối
public class OrderProcessor
{
    private readonly OrderValidator _validator;
    private readonly OrderRepository _repository;
    private readonly EmailService _emailService;
    private readonly InvoiceService _invoiceService;
    private readonly InventoryService _inventoryService;
    
    public OrderProcessor(
        OrderValidator validator,
        OrderRepository repository,
        EmailService emailService,
        InvoiceService invoiceService,
        InventoryService inventoryService)
    {
        _validator = validator;
        _repository = repository;
        _emailService = emailService;
        _invoiceService = invoiceService;
        _inventoryService = inventoryService;
    }
    
    public void ProcessOrder(Order order)
    {
        if (!_validator.Validate(order))
            throw new InvalidOrderException();
        
        _repository.Save(order);
        _emailService.SendOrderConfirmation(order);
        _invoiceService.Generate(order);
        _inventoryService.Update(order);
    }
}

Thực hành với ASP.NET Core

// ✅ SRP trong Controller
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;
    
    public OrdersController(IOrderService orderService)
    {
        _orderService = orderService;
    }
    
    [HttpPost]
    public async Task<IActionResult> Create([FromBody] CreateOrderDto dto)
    {
        // Controller chỉ xử lý HTTP concerns
        var result = await _orderService.CreateOrderAsync(dto);
        return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
    }
}

// Service xử lý business logic
public class OrderService : IOrderService
{
    private readonly IOrderValidator _validator;
    private readonly IOrderRepository _repository;
    private readonly IInventoryService _inventoryService;
    
    public async Task<OrderResult> CreateOrderAsync(CreateOrderDto dto)
    {
        // Business logic tập trung ở đây
        var order = Order.Create(dto);
        
        if (!await _validator.ValidateAsync(order))
            throw new ValidationException("Invalid order");
        
        await _repository.AddAsync(order);
        await _inventoryService.ReserveItemsAsync(order.Items);
        
        return OrderResult.FromOrder(order);
    }
}

2. Open/Closed Principle (OCP)

Định nghĩa

“Các entities (class, module, function) nên mở cho việc mở rộng nhưng đóng cho việc sửa đổi.”

Tại sao cần OCP?

  • Giảm rủi ro khi thêm tính năng mới
  • Dễ dàng mở rộng mà không ảnh hưởng đến code hiện có
  • Tuân thủ OCP thường dẫn đến thiết kế tốt hơn

Ví dụ vi phạm OCP

// ❌ VI PHẠM - Phải sửa code khi thêm payment method mới
public class PaymentProcessor
{
    public void ProcessPayment(Payment payment)
    {
        switch (payment.Type)
        {
            case PaymentType.CreditCard:
                ProcessCreditCard(payment);
                break;
            case PaymentType.PayPal:
                ProcessPayPal(payment);
                break;
            case PaymentType.BankTransfer:
                ProcessBankTransfer(payment);
                break;
            // Thêm method mới => phải sửa switch statement!
            default:
                throw new ArgumentException("Unsupported payment type");
        }
    }
    
    private void ProcessCreditCard(Payment payment) { /* logic */ }
    private void ProcessPayPal(Payment payment) { /* logic */ }
    private void ProcessBankTransfer(Payment payment) { /* logic */ }
}

Ví dụ tuân thủ OCP

// ✅ TUÂN THỦ - Sử dụng abstraction và polymorphism
public interface IPaymentMethod
{
    Task<PaymentResult> ProcessAsync(decimal amount);
}

public class CreditCardPayment : IPaymentMethod
{
    public async Task<PaymentResult> ProcessAsync(decimal amount)
    {
        // Credit card processing logic
        return new PaymentResult { Success = true };
    }
}

public class PayPalPayment : IPaymentMethod
{
    public async Task<PaymentResult> ProcessAsync(decimal amount)
    {
        // PayPal processing logic
        return new PaymentResult { Success = true };
    }
}

public class BankTransferPayment : IPaymentMethod
{
    public async Task<PaymentResult> ProcessAsync(decimal amount)
    {
        // Bank transfer logic
        return new PaymentResult { Success = true };
    }
}

// Thêm payment method mới không cần sửa PaymentProcessor
public class CryptoPayment : IPaymentMethod
{
    public async Task<PaymentResult> ProcessAsync(decimal amount)
    {
        // Cryptocurrency processing logic
        return new PaymentResult { Success = true };
    }
}

public class PaymentProcessor
{
    private readonly Dictionary<PaymentType, IPaymentMethod> _paymentMethods;
    
    public PaymentProcessor()
    {
        _paymentMethods = new Dictionary<PaymentType, IPaymentMethod>
        {
            { PaymentType.CreditCard, new CreditCardPayment() },
            { PaymentType.PayPal, new PayPalPayment() },
            { PaymentType.BankTransfer, new BankTransferPayment() },
            { PaymentType.Crypto, new CryptoPayment() } // Thêm mới dễ dàng
        };
    }
    
    public async Task<PaymentResult> ProcessPayment(Payment payment)
    {
        if (_paymentMethods.TryGetValue(payment.Type, out var paymentMethod))
        {
            return await paymentMethod.ProcessAsync(payment.Amount);
        }
        
        throw new ArgumentException("Unsupported payment type");
    }
}

Strategy Pattern với OCP

// Sử dụng Strategy Pattern để tuân thủ OCP
public interface IDiscountStrategy
{
    decimal CalculateDiscount(decimal originalPrice);
}

public class NoDiscountStrategy : IDiscountStrategy
{
    public decimal CalculateDiscount(decimal originalPrice) => originalPrice;
}

public class PercentageDiscountStrategy : IDiscountStrategy
{
    private readonly decimal _percentage;
    
    public PercentageDiscountStrategy(decimal percentage)
    {
        _percentage = percentage;
    }
    
    public decimal CalculateDiscount(decimal originalPrice)
        => originalPrice * (1 - _percentage / 100);
}

public class FixedAmountDiscountStrategy : IDiscountStrategy
{
    private readonly decimal _amount;
    
    public FixedAmountDiscountStrategy(decimal amount)
    {
        _amount = amount;
    }
    
    public decimal CalculateDiscount(decimal originalPrice)
        => Math.Max(0, originalPrice - _amount);
}

public class PriceCalculator
{
    private readonly IDiscountStrategy _discountStrategy;
    
    public PriceCalculator(IDiscountStrategy discountStrategy)
    {
        _discountStrategy = discountStrategy;
    }
    
    public decimal CalculateFinalPrice(decimal originalPrice)
    {
        return _discountStrategy.CalculateDiscount(originalPrice);
    }
}

// Sử dụng
var calculator = new PriceCalculator(new PercentageDiscountStrategy(20));
var finalPrice = calculator.CalculateFinalPrice(100); // 80

3. Liskov Substitution Principle (LSP)

Định nghĩa

“Các objects của subclass phải có thể thay thế objects của parent class mà không làm thay đổi tính đúng đắn của chương trình.”

Tại sao cần LSP?

  • Đảm bảo tính đúng đắn của inheritance hierarchy
  • Ngăn ngừa lỗi runtime khi thay thế objects
  • Giúp thiết kế interface hợp lý hơn

Ví dụ vi phạm LSP (Hình học cổ điển)

public class Rectangle
{
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }
    
    public int Area => Width * Height;
}

public class Square : Rectangle
{
    private int _side;
    
    public override int Width
    {
        get => _side;
        set { _side = value; Height = value; }
    }
    
    public override int Height
    {
        get => _side;
        set { _side = value; Width = value; }
    }
}

// ❌ VI PHẠM LSP - Square không thể thay thế Rectangle
void TestRectangleArea(Rectangle rect)
{
    rect.Width = 5;
    rect.Height = 4;
    
    // Với Rectangle: 5 * 4 = 20
    // Với Square: 4 * 4 = 16 (sau khi set Height = 4, Width cũng thành 4)
    Console.WriteLine($"Area: {rect.Area}");
}

var rectangle = new Rectangle();
TestRectangleArea(rectangle); // Area: 20 ✅

var square = new Square();
TestRectangleArea(square); // Area: 16 ❌ (không đúng kỳ vọng)

Giải pháp cho LSP

// ✅ TUÂN THỦ - Sử dụng interface hoặc abstract class chung
public interface IShape
{
    int Area { get; }
}

public class Rectangle : IShape
{
    public int Width { get; set; }
    public int Height { get; set; }
    
    public int Area => Width * Height;
}

public class Square : IShape
{
    public int Side { get; set; }
    
    public int Area => Side * Side;
}

// Hoặc sử dụng composition thay vì inheritance
public abstract class Shape
{
    public abstract int Area { get; }
}

public class Rectangle : Shape
{
    public int Width { get; }
    public int Height { get; }
    
    public Rectangle(int width, int height)
    {
        Width = width;
        Height = height;
    }
    
    public override int Area => Width * Height;
}

public class Square : Shape
{
    public int Side { get; }
    
    public Square(int side)
    {
        Side = side;
    }
    
    public override int Area => Side * Side;
}

Ví dụ thực tế với Repository Pattern

// ❌ VI PHẠM - Repository vi phạm LSP
public interface IRepository<T>
{
    T GetById(int id);
    void Save(T entity);
    void Delete(int id);
}

public class UserRepository : IRepository<User>
{
    public User GetById(int id) { /* logic */ }
    public void Save(User user) { /* logic */ }
    public void Delete(int id) { /* logic */ }
}

public class ReadOnlyUserRepository : IRepository<User>
{
    public User GetById(int id) { /* logic */ }
    
    public void Save(User user)
    {
        throw new NotSupportedException("Read-only repository");
    }
    
    public void Delete(int id)
    {
        throw new NotSupportedException("Read-only repository");
    }
}

// ✅ TUÂN THỦ - Tách interface
public interface IReadRepository<T>
{
    T GetById(int id);
    IEnumerable<T> GetAll();
}

public interface IWriteRepository<T>
{
    void Save(T entity);
    void Delete(int id);
}

public interface IRepository<T> : IReadRepository<T>, IWriteRepository<T>
{
}

public class UserRepository : IRepository<User>
{
    // Implement cả read và write
}

public class ReadOnlyUserRepository : IReadRepository<User>
{
    // Chỉ implement read operations
    public User GetById(int id) { /* logic */ }
    public IEnumerable<User> GetAll() { /* logic */ }
}

4. Interface Segregation Principle (ISP)

Định nghĩa

“Client không nên bị buộc phải phụ thuộc vào các interface mà họ không sử dụng.”

Tại sao cần ISP?

  • Giảm sự phụ thuộc không cần thiết
  • Tránh “fat interfaces” với nhiều methods không liên quan
  • Dễ dàng test và mock

Ví dụ vi phạm ISP

// ❌ VI PHẠM - Fat interface
public interface IWorker
{
    void Work();
    void Eat();
    void Sleep();
    void Code();
    void Test();
    void Deploy();
}

public class HumanWorker : IWorker
{
    public void Work() { Console.WriteLine("Working..."); }
    public void Eat() { Console.WriteLine("Eating..."); }
    public void Sleep() { Console.WriteLine("Sleeping..."); }
    public void Code() { Console.WriteLine("Coding..."); }
    public void Test() { Console.WriteLine("Testing..."); }
    public void Deploy() { Console.WriteLine("Deploying..."); }
}

public class RobotWorker : IWorker
{
    public void Work() { Console.WriteLine("Working..."); }
    public void Eat() { /* Robot doesn't eat! */ }
    public void Sleep() { /* Robot doesn't sleep! */ }
    public void Code() { Console.WriteLine("Coding..."); }
    public void Test() { Console.WriteLine("Testing..."); }
    public void Deploy() { Console.WriteLine("Deploying..."); }
}

Ví dụ tuân thủ ISP

// ✅ TUÂN THỦ - Tách thành các interfaces nhỏ
public interface IWorkable
{
    void Work();
}

public interface IFeedable
{
    void Eat();
}

public interface ISleepable
{
    void Sleep();
}

public interface ICodeable
{
    void Code();
}

public interface ITestable
{
    void Test();
}

public interface IDeployable
{
    void Deploy();
}

// Human có thể làm mọi thứ
public class HumanWorker : IWorkable, IFeedable, ISleepable, ICodeable, ITestable, IDeployable
{
    public void Work() { Console.WriteLine("Working..."); }
    public void Eat() { Console.WriteLine("Eating..."); }
    public void Sleep() { Console.WriteLine("Sleeping..."); }
    public void Code() { Console.WriteLine("Coding..."); }
    public void Test() { Console.WriteLine("Testing..."); }
    public void Deploy() { Console.WriteLine("Deploying..."); }
}

// Robot chỉ làm việc liên quan đến coding
public class RobotWorker : IWorkable, ICodeable, ITestable, IDeployable
{
    public void Work() { Console.WriteLine("Working..."); }
    public void Code() { Console.WriteLine("Coding..."); }
    public void Test() { Console.WriteLine("Testing..."); }
    public void Deploy() { Console.WriteLine("Deploying..."); }
}

ISP trong ASP.NET Core

// ❌ VI PHẠM - Service interface quá lớn
public interface IOrderService
{
    Task<Order> CreateOrderAsync(CreateOrderDto dto);
    Task<Order> GetOrderAsync(int id);
    Task<IEnumerable<Order>> GetOrdersAsync();
    Task UpdateOrderAsync(int id, UpdateOrderDto dto);
    Task DeleteOrderAsync(int id);
    Task<Invoice> GenerateInvoiceAsync(int orderId);
    Task SendOrderConfirmationAsync(int orderId);
    Task CancelOrderAsync(int orderId);
    Task RefundOrderAsync(int orderId);
    Task<OrderStatistics> GetStatisticsAsync(DateTime from, DateTime to);
}

// ✅ TUÂN THỦ - Tách thành các interfaces chuyên biệt
public interface IOrderCommandService
{
    Task<Order> CreateOrderAsync(CreateOrderDto dto);
    Task UpdateOrderAsync(int id, UpdateOrderDto dto);
    Task DeleteOrderAsync(int id);
    Task CancelOrderAsync(int orderId);
    Task RefundOrderAsync(int orderId);
}

public interface IOrderQueryService
{
    Task<Order> GetOrderAsync(int id);
    Task<IEnumerable<Order>> GetOrdersAsync();
    Task<OrderStatistics> GetStatisticsAsync(DateTime from, DateTime to);
}

public interface IOrderNotificationService
{
    Task SendOrderConfirmationAsync(int orderId);
}

public interface IOrderDocumentService
{
    Task<Invoice> GenerateInvoiceAsync(int orderId);
}

// Có thể implement riêng biệt
public class OrderService : IOrderCommandService, IOrderQueryService
{
    // Implement các methods
}

// Hoặc tách hoàn toàn (CQRS)
public class OrderCommandService : IOrderCommandService { }
public class OrderQueryService : IOrderQueryService { }

5. Dependency Inversion Principle (DIP)

Định nghĩa

  1. Các module cấp cao không nên phụ thuộc vào module cấp thấp. Cả hai nên phụ thuộc vào abstraction.
  2. Abstraction không nên phụ thuộc vào chi tiết. Chi tiết nên phụ thuộc vào abstraction.

Tại sao cần DIP?

  • Giảm coupling giữa các components
  • Dễ dàng test với mock objects
  • Linh hoạt thay đổi implementation
  • Tuân thủ Dependency Injection

Ví dụ vi phạm DIP

// ❌ VI PHẠM - High-level module phụ thuộc vào low-level module
public class OrderService
{
    private readonly SqlServerOrderRepository _repository;
    private readonly SmtpEmailService _emailService;
    private readonly FileSystemLogger _logger;
    
    public OrderService()
    {
        _repository = new SqlServerOrderRepository();
        _emailService = new SmtpEmailService();
        _logger = new FileSystemLogger();
    }
    
    public void ProcessOrder(Order order)
    {
        _repository.Save(order);
        _emailService.SendConfirmation(order);
        _logger.Log($"Order {order.Id} processed");
    }
}

// Low-level modules
public class SqlServerOrderRepository
{
    public void Save(Order order) { /* SQL Server logic */ }
}

public class SmtpEmailService
{
    public void SendConfirmation(Order order) { /* SMTP logic */ }
}

public class FileSystemLogger
{
    public void Log(string message) { /* File system logic */ }
}

Ví dụ tuân thủ DIP

// ✅ TUÂN THỦ - Cả high-level và low-level phụ thuộc vào abstraction
public interface IOrderRepository
{
    Task SaveAsync(Order order);
}

public interface IEmailService
{
    Task SendConfirmationAsync(Order order);
}

public interface ILogger
{
    Task LogAsync(string message);
}

// High-level module
public class OrderService
{
    private readonly IOrderRepository _repository;
    private readonly IEmailService _emailService;
    private readonly ILogger _logger;
    
    public OrderService(
        IOrderRepository repository,
        IEmailService emailService,
        ILogger logger)
    {
        _repository = repository;
        _emailService = emailService;
        _logger = logger;
    }
    
    public async Task ProcessOrderAsync(Order order)
    {
        await _repository.SaveAsync(order);
        await _emailService.SendConfirmationAsync(order);
        await _logger.LogAsync($"Order {order.Id} processed");
    }
}

// Low-level modules phụ thuộc vào abstraction
public class SqlServerOrderRepository : IOrderRepository
{
    public async Task SaveAsync(Order order)
    {
        // SQL Server implementation
    }
}

public class SmtpEmailService : IEmailService
{
    public async Task SendConfirmationAsync(Order order)
    {
        // SMTP implementation
    }
}

public class FileSystemLogger : ILogger
{
    public async Task LogAsync(string message)
    {
        // File system implementation
    }
}

// Có thể dễ dàng thay thế implementation
public class MongoDbOrderRepository : IOrderRepository
{
    public async Task SaveAsync(Order order)
    {
        // MongoDB implementation
    }
}

public class SendGridEmailService : IEmailService
{
    public async Task SendConfirmationAsync(Order order)
    {
        // SendGrid implementation
    }
}

public class CloudLogger : ILogger
{
    public async Task LogAsync(string message)
    {
        // Cloud logging implementation
    }
}

DIP với Dependency Injection trong ASP.NET Core

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Đăng ký dependencies
builder.Services.AddScoped<IOrderRepository, SqlServerOrderRepository>();
builder.Services.AddScoped<IEmailService, SendGridEmailService>();
builder.Services.AddScoped<ILogger, CloudLogger>();
builder.Services.AddScoped<IOrderService, OrderService>();

// Controller sử dụng DI
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orderService;
    
    public OrdersController(IOrderService orderService)
    {
        _orderService = orderService;
    }
    
    [HttpPost]
    public async Task<IActionResult> CreateOrder([FromBody] CreateOrderDto dto)
    {
        var order = await _orderService.CreateOrderAsync(dto);
        return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
    }
}

// Factory Pattern với DI
public interface IReportGenerator
{
    Task<Report> GenerateAsync(ReportData data);
}

public class PdfReportGenerator : IReportGenerator { /* PDF logic */ }
public class ExcelReportGenerator : IReportGenerator { /* Excel logic */ }
public class HtmlReportGenerator : IReportGenerator { /* HTML logic */ }

public class ReportService
{
    private readonly IServiceProvider _serviceProvider;
    
    public ReportService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }
    
    public async Task<Report> GenerateReportAsync(ReportType type, ReportData data)
    {
        var generator = type switch
        {
            ReportType.Pdf => _serviceProvider.GetRequiredService<PdfReportGenerator>(),
            ReportType.Excel => _serviceProvider.GetRequiredService<ExcelReportGenerator>(),
            ReportType.Html => _serviceProvider.GetRequiredService<HtmlReportGenerator>(),
            _ => throw new ArgumentException($"Unsupported report type: {type}")
        };
        
        return await generator.GenerateAsync(data);
    }
}

Tổng kết và Best Practices

Khi nào áp dụng SOLID?

  1. SRP: Khi một class đang làm quá nhiều việc, khó test, khó thay đổi
  2. OCP: Khi cần thêm tính năng mới mà không muốn sửa code hiện có
  3. LSP: Khi thiết kế inheritance hierarchy, đặc biệt với abstract class
  4. ISP: Khi interface có quá nhiều methods không liên quan
  5. DIP: Khi muốn giảm coupling, dễ test, dễ thay đổi implementation

Mối quan hệ giữa các nguyên tắc

  • DIP thường dẫn đến ISP (tách interface nhỏ)
  • OCP thường đạt được thông qua DIP (phụ thuộc abstraction)
  • LSP đảm bảo inheritance hierarchy đúng đắn
  • SRP là nền tảng cho tất cả các nguyên tắc khác

Code smells vi phạm SOLID

  1. God Class: Class quá lớn, làm nhiều việc (vi phạm SRP)
  2. Switch Statements: Nhiều switch/case xử lý các type khác nhau (vi phạm OCP)
  3. Empty Method Overrides: Override method để throw exception (vi phạm LSP)
  4. Interface Pollution: Interface với nhiều methods không liên quan (vi phạm ISP)
  5. Concrete Dependencies: Direct instantiation của concrete classes (vi phạm DIP)

Tools hỗ trợ

  1. SonarQube: Phát hiện code smells vi phạm SOLID
  2. ReSharper: Gợi ý refactor để tuân thủ SOLID
  3. Visual Studio Code Metrics: Đo lường maintainability index
  4. Unit Tests: Kiểm tra tính đúng đắn khi refactor

Thực hành trong dự án thực tế

// Ví dụ tổng hợp SOLID trong ASP.NET Core
public interface IRepository<T> : IReadRepository<T>, IWriteRepository<T>
{
    // ISP: Tách read/write
}

public abstract class BaseRepository<T> : IRepository<T>
{
    // DIP: Phụ thuộc DbContext abstraction
    protected readonly DbContext _context;
    
    protected BaseRepository(DbContext context)
    {
        _context = context;
    }
    
    // SRP: Mỗi method làm một việc
    public virtual async Task<T> GetByIdAsync(int id)
    {
        return await _context.Set<T>().FindAsync(id);
    }
    
    // OCP: Virtual method cho phép override
    public virtual async Task AddAsync(T entity)
    {
        await _context.Set<T>().AddAsync(entity);
        await _context.SaveChangesAsync();
    }
}

// LSP: Có thể thay thế bằng specialization
public class AuditableRepository<T> : BaseRepository<T> where T : class, IAuditable
{
    public AuditableRepository(DbContext context) : base(context) { }
    
    public override async Task AddAsync(T entity)
    {
        entity.CreatedAt = DateTime.UtcNow;
        entity.CreatedBy = GetCurrentUserId();
        await base.AddAsync(entity);
    }
}

SOLID không phải là mục tiêu cuối cùng mà là phương tiện để đạt được code chất lượng cao. Áp dụng SOLID đúng cách sẽ tạo ra hệ thống dễ bảo trì, mở rộng và test.