SOLID bị dạy quá giáo điều: "phải luôn tuân thủ". Thực tế, SOLID là công cụ tư duy, không phải luật. Senior/master biết khi nào áp dụng, khi nào vi phạm có chủ đích.
Mục tiêu
- Hiểu bản chất từng principle (không phải thuộc lòng định nghĩa)
- Biết khi nào SOLID giúp ích, khi nào nó over-engineering
- Review code qua lăng kính SOLID thực chiến
S - Single Responsibility: "1 Lý Do Để Thay Đổi"
csharp// ❌ Class có NHIỀU lý do thay đổi:
public class OrderService
{
public Order CreateOrder(OrderRequest req) { /* business logic */ }
public void SendConfirmationEmail(Order order) { /* email logic */ }
public decimal CalculateDiscount(Order order) { /* pricing logic */ }
public string GenerateInvoicePdf(Order order) { /* PDF logic */ }
}
// → Email thay đổi? Sửa OrderService.
// → Discount rules đổi? Sửa OrderService.
// → PDF format đổi? Sửa OrderService.
// → 1 class bị sửa vì 4 lý do khác nhau!
// ✅ Tách theo "lý do thay đổi":
public class OrderService { /* Chỉ tạo/quản lý orders */ }
public class EmailService { /* Chỉ gửi email */ }
public class PricingService { /* Chỉ tính giá/discount */ }
public class InvoiceGenerator { /* Chỉ tạo PDF */ }
Nhưng đừng quá đà:
csharp// ❌ Over-SRP: tách quá nhỏ, class chỉ có 1 method
public class OrderValidator { void Validate(Order o) { } }
public class OrderSaver { void Save(Order o) { } }
public class OrderLogger { void Log(Order o) { } }
// → 20 tiny classes cho 1 feature → khó navigate, khó hiểu flow
// ✅ Thực chiến: tách theo BUSINESS CONCERN, không phải method
// OrderService xử lý multiple methods CÙNG concern (quản lý orders) = OK
O - Open/Closed: "Mở Rộng Không Cần Sửa Code Cũ"
csharp// ❌ Thêm loại discount → phải sửa method cũ
public decimal CalculateDiscount(Order order)
{
if (order.CustomerType == "VIP") return 0.2m;
if (order.CustomerType == "Gold") return 0.1m;
if (order.CustomerType == "New") return 0.05m;
// Thêm loại mới → sửa if/else ở đây → vi phạm OCP
return 0m;
}
// ✅ Strategy pattern: thêm loại mới = thêm class mới
public interface IDiscountStrategy
{
bool CanApply(Order order);
decimal Calculate(Order order);
}
public class VipDiscount : IDiscountStrategy { /* ... */ }
public class GoldDiscount : IDiscountStrategy { /* ... */ }
// Thêm StudentDiscount → tạo class mới, KHÔNG sửa code cũ
public class PricingService(IEnumerable<IDiscountStrategy> strategies)
{
public decimal CalculateDiscount(Order order)
{
return strategies
.Where(s => s.CanApply(order))
.Max(s => s.Calculate(order));
}
}
Nhưng đừng quá đà:
csharp// ❌ Over-OCP: Strategy pattern cho 2 cases
// Nếu chỉ có 2 loại discount và KHÔNG CÓ KẼ HỞ sẽ thêm → if/else HOÀN TOÀN OK!
// Strategy pattern thêm complexity (interface, DI, multiple files) cho giá trị thấp
L - Liskov Substitution: "Subclass Thay Thế Được Base Class"
csharp// ❌ Vi phạm LSP kinh đ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
{
public override int Width
{
set { base.Width = value; base.Height = value; } // Force equal!
}
public override int Height
{
set { base.Height = value; base.Width = value; } // Force equal!
}
}
// Code dùng Rectangle → bất ngờ khi nhận Square:
Rectangle r = new Square();
r.Width = 5;
r.Height = 3;
Console.WriteLine(r.Area); // Mong 15, nhận 9! 💀
// ✅ Fix: Composition, không inheritance sai
public interface IShape { int Area { get; } }
public record Rectangle(int Width, int Height) : IShape { public int Area => Width * Height; }
public record Square(int Side) : IShape { public int Area => Side * Side; }
Mindset thực chiến: Khi kế thừa, hỏi: "Caller dùng base class có bị surprise khi nhận subclass không?" Nếu có → đừng kế thừa.
I - Interface Segregation: "Interface Nhỏ, Hành Vi Rõ"
csharp// ❌ Fat interface: caller bị buộc implement methods không cần
public interface IRepository<T>
{
T GetById(int id);
List<T> GetAll();
void Add(T entity);
void Update(T entity);
void Delete(T entity);
List<T> Search(string query);
void BulkInsert(List<T> entities);
void ExecuteRawSql(string sql);
}
// → ReadOnlyService chỉ cần GetById và GetAll,
// nhưng phải implement Add, Update, Delete (throw NotImplementedException?)
// ✅ Tách interface theo USE CASE
public interface IReadRepository<T>
{
T? GetById(int id);
List<T> GetAll();
}
public interface IWriteRepository<T>
{
void Add(T entity);
void Update(T entity);
void Delete(T entity);
}
// Consumer chỉ inject cái họ CẦN
public class ProductListController(IReadRepository<Product> repo) { }
public class ProductAdminController(IReadRepository<Product> reader, IWriteRepository<Product> writer) { }
D - Dependency Inversion: "Depend Vào Abstraction"
csharp// ❌ High-level module depend vào low-level detail
public class OrderService
{
private readonly SqlOrderRepository _repo = new(); // Trực tiếp depend SQL!
private readonly SmtpEmailService _email = new(); // Trực tiếp depend SMTP!
}
// → Đổi database? Sửa OrderService.
// → Test? Phải có SQL server thật!
// ✅ Depend vào abstraction (interface)
public class OrderService(IOrderRepository repo, IEmailService email)
{
// KHÔNG biết implementation là SQL hay MongoDB
// KHÔNG biết email là SMTP hay SendGrid
// → Dễ test (mock), dễ thay đổi
}
Khi Nào SOLID Là Overkill?
✅ Dùng SOLID khi:
- Code sẽ được NHIỀU NGƯỜI maintain
- Yêu cầu THAY ĐỔI thường xuyên (business rules, integrations)
- Cần TEST isolation
❌ SOLID quá mức khi:
- Prototype / MVP (ship nhanh, refactor sau)
- Script 1 lần (ETL, migration)
- Simple CRUD (không có business logic phức tạp)
- Team nhỏ, 1-2 người hiểu context
Quy tắc 3: Nếu pattern chỉ giúp khi có ≥3 variations → đợi đến lúc cần variation thứ 3 rồi refactor.
Master Checklist ✅
□ Class có >1 lý do thay đổi? → Tách (SRP)
□ Thêm feature = sửa if/else lớn? → Strategy/Plugin (OCP)
□ Subclass có gây surprise cho caller? → Đừng inherit (LSP)
□ Caller bị buộc implement methods không cần? → Tách interface (ISP)
□ Module depend vào concrete class? → Inject interface (DIP)
□ NHƯNG: quá nhiều interfaces cho CRUD đơn giản? → Simplify!
Tư duy master: SOLID là heuristics, không phải laws. Mỗi principle có "cost" (thêm abstraction, thêm files, thêm complexity). Chỉ trả "cost" đó khi benefit lớn hơn.