SOLID

SOLID 원칙 짧은 소개

Single Responsibility Principle (단일 책임 원칙)

각 클래스는 변경될 이유가 하나만 있어야 한다. 즉 하나의 일, 하나의 책임만 가져야 한다. 그래야 클래스가 집중되고 유지보수하기 쉬워진다.

// 나쁜 예 - 책임이 여러 개
public class Customer
{
    public void SaveToDatabase()
    {
        // database logic
    }

    public void GenerateReport()
    {
        // report logic
    }

    public void CalculateMetrics()
    {
        // metrics logic
    }
}

// 좋은 예 - 단일 책임
public class Customer
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public class CustomerRepository
{
    public void Save(Customer customer)
    {
        // database logic
    }
}

public class CustomerReportGenerator
{
    public void Generate(Customer customer)
    {
        // report logic
    }
}

Open/Closed Principle (개방-폐쇄 원칙)

소프트웨어 요소는 확장에는 열려 있고 변경에는 닫혀 있어야 한다. 기존 코드를 고치지 않고 새 기능을 추가할 수 있어야 한다.

// 나쁜 예
public class PaymentProcessor
{
    public void ProcessPayment(string paymentType)
    {
        if (paymentType == "Credit")
        {
            // process credit payment
        }
        else if (paymentType == "Debit")
        {
            // process debit payment
        }
        // 새 결제 수단을 추가하려면 이 클래스를 고쳐야 한다
    }
}

// 좋은 예
public interface IPaymentMethod
{
    void Process();
}

public class CreditPayment : IPaymentMethod
{
    public void Process()
    {
        // process credit payment
    }
}

public class DebitPayment : IPaymentMethod
{
    public void Process()
    {
        // process debit payment
    }
}

Liskov Substitution Principle (리스코프 치환 원칙)

상위 클래스의 객체는 하위 클래스의 객체로 바꿔도 애플리케이션이 깨지지 않아야 한다. 하위 클래스는 기반 클래스의 동작을 바꾸는 게 아니라 확장해야 한다.

// 나쁜 예
public class Bird
{
    public virtual void Fly()
    {
        // flying implementation
    }
}

public class Penguin : Bird
{
    public override void Fly()
    {
        throw new NotSupportedException("Penguins can't fly!");
    }
}

// 좋은 예
public abstract class Bird
{
    public abstract void Move();
}

public class FlyingBird : Bird
{
    public override void Move()
    {
        // flying implementation
    }
}

public class SwimmingBird : Bird
{
    public override void Move()
    {
        // swimming implementation
    }
}

Interface Segregation Principle (인터페이스 분리 원칙)

클라이언트가 쓰지도 않는 인터페이스에 의존하도록 강요받아서는 안 된다. 범용 인터페이스 하나보다 구체적인 인터페이스 여러 개가 낫다.

// 나쁜 예
public interface IWorker
{
    void Work();
    void Eat();
    void Sleep();
}

// 좋은 예
public interface IWorkable
{
    void Work();
}

public interface IEatable
{
    void Eat();
}

public class Human : IWorkable, IEatable
{
    public void Work()
    {
        // work implementation
    }

    public void Eat()
    {
        // eat implementation
    }
}

Dependency Inversion Principle (의존성 역전 원칙)

고수준 모듈이 저수준 모듈에 의존해서는 안 된다. 둘 다 추상에 의존해야 한다. 추상이 세부사항에 의존해서는 안 되고, 세부사항이 추상에 의존해야 한다.

// 나쁜 예
public class EmailSender
{
    public void SendEmail(string message)
    {
        // send email implementation
    }
}

public class NotificationService
{
    private readonly EmailSender _emailSender;

    public NotificationService()
    {
        _emailSender = new EmailSender(); // 직접 의존
    }
}

// 좋은 예
public interface IMessageSender
{
    void Send(string message);
}

public class EmailSender : IMessageSender
{
    public void Send(string message)
    {
        // send email implementation
    }
}

public class NotificationService
{
    private readonly IMessageSender _sender;

    public NotificationService(IMessageSender sender) // 의존성 주입
    {
        _sender = sender;
    }
}

이 원칙들을 따르면 코드가 이렇게 된다:

  • 유지보수와 테스트가 쉬워진다
  • 변화에 더 유연하게 대응한다
  • 재사용성과 모듈성이 높아진다
  • 수정할 때 버그가 덜 생긴다

핵심은 균형을 찾는 것이다. 이 원칙들은 가치 있는 지침이지만, 단순한 문제에까지 너무 경직되게 적용해 코드를 과도하게 복잡하게 만들어서는 안 된다.