CodeOath
← All posts
Architecture & Patterns80 min total · 26 parts

ACID, SOLID, and Common Design Patterns: A Software Design Reference

Contents — Part 12 of 26: SOLID Applied: Refactoring a Violation-Riddled Class
Part 12 of 26 · ~2 min

SOLID Applied: Refactoring a Violation-Riddled Class

Seeing all five principles violated in one realistic class — and fixed together — makes the connections between them concrete in a way that a five-line summary can't.

// Before: violates SRP (report + notification in one class), OCP (a switch
// that grows for every new notification channel), and DIP (hard-coded
// concrete dependencies constructed inside the class itself).
public class OrderReportService
{
    private readonly SqlConnection _connection = new SqlConnection("...");

    public void GenerateAndNotify(int orderId, string channel)
    {
        var order = _connection.Query<Order>("SELECT * FROM Orders WHERE Id = @id", new { id = orderId }).First();
        var report = $"Order #{order.Id}: ${order.Total}";

        if (channel == "email") SmtpClient.Send("reports@company.com", report);
        else if (channel == "sms") TwilioClient.Send("+15551234567", report);
        // adding "slack" means editing this method again
    }
}
// After: each responsibility is its own class (SRP); new notification
// channels are added by implementing INotifier, not editing existing code
// (OCP); everything depends on interfaces, supplied from outside (DIP).
public interface IOrderRepository { Order GetById(int id); }
public interface INotifier { void Send(string message); }

public class SqlOrderRepository : IOrderRepository
{
    private readonly SqlConnection _connection;
    public SqlOrderRepository(SqlConnection connection) => _connection = connection;
    public Order GetById(int id) =>
        _connection.Query<Order>("SELECT * FROM Orders WHERE Id = @id", new { id }).First();
}

public class EmailNotifier : INotifier
{
    public void Send(string message) => SmtpClient.Send("reports@company.com", message);
}

public class SlackNotifier : INotifier // added later — no existing class touched
{
    public void Send(string message) => SlackClient.PostMessage("#reports", message);
}

public class OrderReportService
{
    private readonly IOrderRepository _repository;
    private readonly INotifier _notifier;

    public OrderReportService(IOrderRepository repository, INotifier notifier)
    {
        _repository = repository;
        _notifier = notifier;
    }

    public void GenerateAndNotify(int orderId)
    {
        var order = _repository.GetById(orderId);
        _notifier.Send($"Order #{order.Id}: ${order.Total}");
    }
}

OrderReportService can now be unit tested with a fake IOrderRepository and a fake INotifier — no real database, no real SMTP server, no real Twilio account required, and no test ever has to be rewritten just because a new notification channel was added elsewhere in the system.