종속성 반전 원칙(Dependency Inversion Principle, DIP)은 SOLID 설계 원칙 중 하나로, 상위 수준 모듈과 하위 수준 모듈 간의 결합도를 낮추는 데 초점을 맞춘 객체 지향 설계 원칙입니다.
종속성 반전 원칙의 핵심 개념
종속성 반전 원칙은 다음 두 가지 규칙으로 요약할 수 있습니다.
- 상위 수준 모듈(고수준 모듈)은 하위 수준 모듈(저수준 모듈)에 의존해서는 안 됩니다. 둘 다 추상화에 의존해야 합니다.
- 추상화는 세부 사항에 의존해서는 안 됩니다. 오히려 세부 사항이 추상화에 의존해야 합니다.
쉽게 말해, 비즈니스 로직을 담당하는 상위 클래스가 구체적인 구현 클래스에 직접 의존하면 코드 변경이 어려워지고 유연성이 떨어집니다. 따라서 인터페이스나 추상 클래스 같은 추상화 계층을 중간에 두어, 상위 모듈과 하위 모듈 모두 이 추상화에 의존하도록 설계해야 합니다.
예제 1: 종속성 반전 적용 전 코드
아래 코드에서 Notification 클래스는 Email과 SMS라는 구체적인 클래스를 직접 생성하고 의존합니다. 새로운 알림 방식(예: 푸시 알림)이 추가되면 Notification 클래스를 수정해야 하므로 OCP(개방-폐쇄 원칙)에도 위배됩니다.
using System;
namespace SolidPrinciples.Dependency.Invertion.Before
{
public class Email
{
public string ToAddress { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public void SendEmail()
{
//이메일 전송
}
}
public class SMS
{
public string PhoneNumber { get; set; }
public string Message { get; set; }
public void SendSMS()
{
//SMS 전송
}
}
public class Notification
{
private Email _email;
private SMS _sms;
public Notification()
{
_email = new Email();
_sms = new SMS();
}
public void Send()
{
_email.SendEmail();
_sms.SendSMS();
}
}
}
예제 2: 종속성 반전 적용 후 코드
이번에는 IMessage라는 인터페이스를 도입하여 문제를 해결해 보겠습니다. Email과 SMS는 모두 IMessage를 구현하며, Notification 클래스는 구체적인 클래스가 아닌 추상화(IMessage)에만 의존합니다.
using System.Collections.Generic;
namespace SolidPrinciples.Dependency.Invertion.After
{
public interface IMessage
{
void SendMessage();
}
public class Email : IMessage
{
public string ToAddress { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public void SendMessage()
{
//이메일 전송
}
}
public class SMS : IMessage
{
public string PhoneNumber { get; set; }
public string Message { get; set; }
public void SendMessage()
{
//SMS 전송
}
}
public class Notification
{
private ICollection<IMessage> _messages;
public Notification(ICollection<IMessage> messages)
{
this._messages = messages;
}
public void Send()
{
foreach (var message in _messages)
{
message.SendMessage();
}
}
}
개선된 코드의 장점
- 확장성 향상: 새로운 알림 채널(예: 푸시 알림, 카카오톡 메시지 등)이 필요하면
IMessage를 구현하는 새 클래스만 추가하면 됩니다. - 느슨한 결합:
Notification클래스는 구체적인 구현을 알지 못하므로, 구현 클래스가 변경되어도 영향을 받지 않습니다. - 테스트 용이성: 단위 테스트 시
IMessage를 구현한 목(Mock) 객체를 주입하여 쉽게 테스트할 수 있습니다. - 생성자 주입 활용: 생성자를 통해 의존성을 외부에서 주입받으므로, DI 컨테이너와 함께 사용하기에도 적합합니다.
마무리
종속성 반전 원칙은 추상화를 중심으로 의존 방향을 역전시켜 코드의 유연성, 확장성, 테스트 용이성을 크게 향상시킵니다. C#에서는 인터페이스와 생성자 주입 패턴을 활용하면 손쉽게 이 원칙을 적용할 수 있으며, 이는 유지보수하기 좋은 견고한 애플리케이션 설계의 기반이 됩니다.