Nguyên tắc Thiết kế
Overview
Các nguyên tắc thiết kế (design principles) là các guidelines giúp tạo ra code clean, maintainable, và scalable. Chúng là nền tảng cho việc xây dựng phần mềm chất lượng cao.
SOLID Principles
1. Single Responsibility Principle (SRP)
Một class chỉ nên có một lý do để thay đổi.
// Bad - Multiple responsibilities
public class User
{
public void Save() { /* Save to database */ }
public void Validate() { /* Validation logic */ }
public void SendEmail() { /* Email sending */ }
public void GenerateReport() { /* Report generation */ }
}
// Good - Single responsibility
public class User { } // Just data
public class UserRepository
{
public void Save(User user) { /* Database */ }
}
public class UserValidator
{
public bool Validate(User user) { /* Validation */ }
}
public class EmailService
{
public void SendEmail(User user) { /* Email */ }
}
2. Open/Closed Principle (OCP)
Entities nên open cho extension nhưng closed cho modification.
// Bad - Need to modify to add new behavior
public class OrderCalculator
{
public decimal Calculate(Order order)
{
if (order.Type == OrderType.Standard)
return order.Amount * 1.0m;
else if (order.Type == OrderType.Premium)
return order.Amount * 0.9m;
// Must add new condition for new types
}
}
// Good - Extend without modification
public interface IDiscountStrategy
{
decimal Calculate(Order order);
}
public class StandardDiscount : IDiscountStrategy
{
public decimal Calculate(Order order) => order.Amount * 1.0m;
}
public class PremiumDiscount : IDiscountStrategy
{
public decimal Calculate(Order order) => order.Amount * 0.9m;
}
public class OrderCalculator
{
private readonly IDiscountStrategy _discountStrategy;
public OrderCalculator(IDiscountStrategy discountStrategy)
{
_discountStrategy = discountStrategy;
}
public decimal Calculate(Order order)
=> _discountStrategy.Calculate(order);
}
3. Liskov Substitution Principle (LSP)
Objects của superclass nên có thể thay thế bằng objects của subclass mà không làm break application.
// Bad - Violates LSP
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; }
}
public override int Height
{
set { base.Width = value; base.Height = value; }
}
}
// This will break!
void CalculateArea(Rectangle rect)
{
rect.Width = 5;
rect.Height = 4;
Console.WriteLine(rect.Area); // Expects 20, but gets 16 for Square!
}
// Good - Proper inheritance
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 Circle : IShape
{
public int Radius { get; set; }
public int Area => (int)(Math.PI * Radius * Radius);
}
4. Interface Segregation Principle (ISP)
Nên prefer many specific interfaces hơn là một interface general.
// Bad - Fat interface
public interface IWorker
{
void Work();
void Eat();
void Sleep();
}
public class Robot : IWorker
{
public void Work() { }
public void Eat() { throw new NotImplementedException(); } // Doesn't eat
public void Sleep() { throw new NotImplementedException(); } // Doesn't sleep
}
// Good - Segregated interfaces
public interface IWorkable
{
void Work();
}
public interface IEatable
{
void Eat();
}
public interface ISleepable
{
void Sleep();
}
public class Human : IWorkable, IEatable, ISleepable
{
public void Work() { }
public void Eat() { }
public void Sleep() { }
}
public class Robot : IWorkable
{
public void Work() { }
}
5. Dependency Inversion Principle (DIP)
Nên depend on abstractions, không phải concretions.
// Bad - Depend on concrete class
public class OrderService
{
private readonly SqlOrderRepository _repository; // Hard dependency
public OrderService()
{
_repository = new SqlOrderRepository();
}
}
// Good - Depend on abstraction
public interface IOrderRepository
{
Task<Order> GetByIdAsync(Guid id);
}
public class OrderService
{
private readonly IOrderRepository _repository; // Abstraction
public OrderService(IOrderRepository repository)
{
_repository = repository;
}
}
// Can inject any implementation
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IOrderRepository, SqlOrderRepository>();
// Or switch to MongoDB easily
// services.AddScoped<IOrderRepository, MongoOrderRepository>();
}
Other Important Principles
DRY - Don’t Repeat Yourself
// Don't repeat logic
public string FormatEmail(string name, string orderId)
{
return $"Dear {name}, your order {orderId} has been placed.";
}
public string FormatEmail(string name, string orderId, string status)
{
return $"Dear {name}, your order {orderId} is now {status}.";
}
// Extract to shared method
public static class EmailFormatter
{
public static string Format(string name, string orderId, string status = null)
{
return status == null
? $"Dear {name}, your order {orderId} has been placed."
: $"Dear {name}, your order {orderId} is now {status}.";
}
}
KISS - Keep It Simple, Stupid
// Simple, readable code over clever code
// Bad
var result = items?.Where(x => x > 0)?.Sum() ?? 0;
// Good - clear intent
if (items == null || items.Count == 0)
return 0;
return items.Where(x => x > 0).Sum();
YAGNI - You Aren’t Gonna Need It
// Don't add functionality that isn't needed yet
// Bad
public interface IUserRepository
{
User GetById(Guid id);
User GetByEmail(string email); // Not used!
User GetByPhone(string phone); // Not used!
List<User> Search(string query); // Not used!
}
// Good - Add as needed
public interface IUserRepository
{
User GetById(Guid id);
// Add more methods when actually needed
}
Law of Demeter
Chỉ nói chuyện với “friends”, không phải “friends of friends”.
// Bad - Violates Law of Demeter
var address = customer.GetAddress().GetCity().GetName();
// Good - Ask for what you need
var cityName = customer.GetCityName();
Summary
| Principle | Description |
|---|---|
| SRP | Một class, một trách nhiệm |
| OCP | Open for extension, closed for modification |
| LSP | Thay thế được subclass |
| ISP | Nhiều interface nhỏ, không interface lớn |
| DIP | Depend on abstractions |