Claude
Skills
Sign in
Back

solid-principles

Included with Lifetime
$97 forever

SOLID design principles for .NET. Use when designing classes, interfaces, and object relationships. Ensures maintainable, testable, and extensible code.

Design

What this skill does


# SOLID Principles for .NET

## Overview

SOLID is an acronym for five principles that lead to maintainable, testable, and extensible object-oriented code.

| Principle | Summary |
|-----------|---------|
| **S** - Single Responsibility | One class, one reason to change |
| **O** - Open/Closed | Open for extension, closed for modification |
| **L** - Liskov Substitution | Subtypes must be substitutable for base types |
| **I** - Interface Segregation | Many specific interfaces > one general interface |
| **D** - Dependency Inversion | Depend on abstractions, not concretions |

---

## S - Single Responsibility Principle (SRP)

> A class should have only one reason to change.

### Violation Example
```csharp
// BAD: Multiple responsibilities
public class OrderService
{
    public Order CreateOrder(OrderRequest request)
    {
        // Validation logic
        if (string.IsNullOrEmpty(request.CustomerEmail))
            throw new ValidationException("Email required");

        // Business logic
        var order = new Order
        {
            Id = Guid.NewGuid(),
            Items = request.Items,
            Total = CalculateTotal(request.Items)
        };

        // Persistence logic
        using var connection = new SqlConnection(_connectionString);
        connection.Execute("INSERT INTO Orders...", order);

        // Notification logic
        var emailBody = $"Order {order.Id} confirmed!";
        _smtpClient.Send(request.CustomerEmail, "Order Confirmed", emailBody);

        // Logging logic
        File.AppendAllText("orders.log", $"{DateTime.Now}: Order {order.Id} created");

        return order;
    }
}
```

### Correct Implementation
```csharp
// GOOD: Single responsibility per class
public class OrderService
{
    private readonly IOrderValidator _validator;
    private readonly IOrderRepository _repository;
    private readonly IOrderNotifier _notifier;
    private readonly ILogger<OrderService> _logger;

    public OrderService(
        IOrderValidator validator,
        IOrderRepository repository,
        IOrderNotifier notifier,
        ILogger<OrderService> logger)
    {
        _validator = validator;
        _repository = repository;
        _notifier = notifier;
        _logger = logger;
    }

    public async Task<Order> CreateOrderAsync(OrderRequest request)
    {
        _validator.Validate(request);

        var order = Order.Create(request.Items);

        await _repository.AddAsync(order);
        await _notifier.NotifyOrderCreatedAsync(order, request.CustomerEmail);

        _logger.LogInformation("Order {OrderId} created", order.Id);

        return order;
    }
}

// Each concern in its own class
public class OrderValidator : IOrderValidator
{
    public void Validate(OrderRequest request)
    {
        if (string.IsNullOrEmpty(request.CustomerEmail))
            throw new ValidationException("Email required");
    }
}

public class OrderRepository : IOrderRepository
{
    private readonly DbContext _context;

    public async Task AddAsync(Order order)
    {
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();
    }
}

public class EmailOrderNotifier : IOrderNotifier
{
    private readonly IEmailService _emailService;

    public async Task NotifyOrderCreatedAsync(Order order, string email)
    {
        await _emailService.SendAsync(email, "Order Confirmed", $"Order {order.Id} confirmed!");
    }
}
```

### SRP Test: Ask These Questions
1. Can I describe what the class does without using "and"?
2. Would different stakeholders want changes to this class?
3. Does the class have more than 200-300 lines?

---

## O - Open/Closed Principle (OCP)

> Software entities should be open for extension but closed for modification.

### Violation Example
```csharp
// BAD: Must modify class to add new discount types
public class DiscountCalculator
{
    public decimal Calculate(Order order, string discountType)
    {
        switch (discountType)
        {
            case "percentage":
                return order.Total * 0.1m;
            case "fixed":
                return 10m;
            case "loyalty":
                return order.Total * 0.15m;
            // Every new discount requires modifying this class
            default:
                return 0m;
        }
    }
}
```

### Correct Implementation
```csharp
// GOOD: Extensible without modification
public interface IDiscountStrategy
{
    decimal Calculate(Order order);
}

public class PercentageDiscount : IDiscountStrategy
{
    private readonly decimal _percentage;

    public PercentageDiscount(decimal percentage) => _percentage = percentage;

    public decimal Calculate(Order order) => order.Total * _percentage;
}

public class FixedDiscount : IDiscountStrategy
{
    private readonly decimal _amount;

    public FixedDiscount(decimal amount) => _amount = amount;

    public decimal Calculate(Order order) => Math.Min(_amount, order.Total);
}

public class LoyaltyDiscount : IDiscountStrategy
{
    private readonly ILoyaltyService _loyaltyService;

    public LoyaltyDiscount(ILoyaltyService loyaltyService) => _loyaltyService = loyaltyService;

    public decimal Calculate(Order order)
    {
        var tier = _loyaltyService.GetCustomerTier(order.CustomerId);
        return tier switch
        {
            LoyaltyTier.Gold => order.Total * 0.15m,
            LoyaltyTier.Silver => order.Total * 0.10m,
            _ => 0m
        };
    }
}

// New discounts added without touching existing code
public class BulkDiscount : IDiscountStrategy
{
    public decimal Calculate(Order order)
    {
        if (order.Items.Count >= 10)
            return order.Total * 0.20m;
        return 0m;
    }
}

// Calculator is closed for modification
public class DiscountCalculator
{
    public decimal Calculate(Order order, IDiscountStrategy strategy)
    {
        return strategy.Calculate(order);
    }
}
```

### OCP Patterns
- Strategy Pattern (as shown above)
- Template Method Pattern
- Decorator Pattern
- Plugin Architecture

---

## L - Liskov Substitution Principle (LSP)

> Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.

### Violation Example
```csharp
// BAD: Square violates Rectangle's contract
public class Rectangle
{
    public virtual int Width { get; set; }
    public virtual int Height { get; set; }

    public int CalculateArea() => Width * Height;
}

public class Square : Rectangle
{
    public override int Width
    {
        get => base.Width;
        set
        {
            base.Width = value;
            base.Height = value; // Unexpected side effect!
        }
    }

    public override int Height
    {
        get => base.Height;
        set
        {
            base.Height = value;
            base.Width = value; // Unexpected side effect!
        }
    }
}

// This test fails for Square!
[Fact]
public void Rectangle_SetDimensions_CalculatesCorrectArea()
{
    Rectangle rect = new Square(); // Substitution
    rect.Width = 5;
    rect.Height = 4;
    Assert.Equal(20, rect.CalculateArea()); // Fails! Returns 16
}
```

### Correct Implementation
```csharp
// GOOD: Separate abstractions
public interface IShape
{
    int CalculateArea();
}

public class Rectangle : IShape
{
    public int Width { get; }
    public int Height { get; }

    public Rectangle(int width, int height)
    {
        Width = width;
        Height = height;
    }

    public int CalculateArea() => Width * Height;
}

public class Square : IShape
{
    public int Side { get; }

    public Square(int side) => Side = side;

    public int CalculateArea() => Side * Side;
}

// Both work correctly with the abstraction
public class AreaCalculator
{
    public int TotalArea(IEnumerable<IShape> shapes)
    {
        return shapes.Sum(s => s.CalculateArea());
    }
}
```

### LSP Rules
1. Preconditions cannot be strengthened in subtype
2. Postconditions cannot be weakened in subtype
3. Invariants must be

Related in Design