1. 의존성이 왜 문제인가? 시스템이 망가지는 구조의 시작
애플리케이션 개발을 하다 보면, 단순한 기능 변경이 시스템 전반에 영향을 미치는 상황을 자주 경험하게 된다. 예를 들어, 주문 처리 후 완료 시 이메일을 발송하는 기능을 처음에는 단순하게 구현했다고 가정하자. 그런데 시간이 지나면서 SMS나 푸시 알림도 함께 발송하라는 요구가 추가되기 마련이다.
이러한 변화에 대응하기 위해 많은 개발자는 기존의 이메일 발송 코드에 SMS나 푸시 알림 전송 로직을 그대로 덧붙이는 방식을 선택한다. 처음에는 빠르게 동작하므로 문제가 없어 보일 수 있지만, 이런 방식은 시스템의 복잡도를 빠르게 증가시킨다. 가장 큰 문제는 비즈니스 로직이 특정 기술 구현에 직접 의존하게 된다는 점이다. 다시 말해, '사용자 등록'과 같은 고수준 로직이, '이메일 발송'이라는 저수준 구현에 직접 연결되는 구조가 된다.
소프트웨어 아키텍처에서 흔히 다음과 같은 구분을 사용한다.
- 고수준 모듈(High-level module) : 애플리케이션의 핵심 로직을 담당하며, 시스템의 정책을 정의한다.
- 저수준 모듈(Low-level module) : 구체적인 동작을 수행하는 기술적 구현을 의미한다.
고수준 모듈이 저수준 모듈에 직접 의존하게 되면 다음과 같은 문제가 발생한다.
- 이메일 발송 방식이 바뀌면 사용자 등록 로직도 함께 수정해야 한다.
- 테스트 환경에서 실제 이메일이 발송되어 단위 테스트가 어렵다.
- 알림 수단이 늘어날수록 서비스 클래스의 수정 범위가 커지고, 이로 인해 오류 가능성도 증가한다.
이러한 구조는 처음에는 단순하고 직관적으로 보일 수 있지만, 시스템이 확장되면서 작은 변화가 전체 시스템에 영향을 미치는 구조적 리스크로 발전하게 된다. 고수준 로직이 저수준 구현 세부사항에 종속되어 있기 때문이다. 결국 시스템은 변화에 취약한 형태로 굳어진다.
이를 해결하려면, 비즈니스 로직과 기술 구현을 분리하고, 그 사이에 역할만을 정의하는 추상화 계층을 둬야 한다. 즉, "무엇을 해야 하는가?"와 "어떻게 할 것인가?"를 구분하는 것이다. 이러한 설계 전환을 가능하게 해주는 원칙이 바로 의존성 역전 원칙(DIP, Dependency Inversion Principle)이다.

2. DIP란 무엇인가? 정의 및 현실 비유
2-1. DIP 정의 및 원칙
의존성 역전 원칙(DIP, Dependency Inversion Principle)은 다음과 같이 정의된다.
"고수준 모듈은 저수준 모듈에 의존해서는 안 된다.
이 둘 모두 추상화에 의존해야 하며, 추상화는 구체적인 것에 의존해서는 안 된다.
오히려 구체적인 것이 추상화에 의존해야 한다."
이 정의는 다소 추상적이고 이론적으로 보일 수 있다. 하지만 각 문장을 분해해 보면, 실무에서도 매우 실질적인 설계 기준이라는 것을 확인할 수 있다.
- 💡 고수준 모듈은 저수준 모듈에 의존해서는 안 된다.
만약 온라인 쇼핑몰의 비즈니스 로직이 담겨 있는 OrderService가 EmailNotifier라는 구체 클래스를 직접 생성하거나 호출한다면, 이메일 발송 방식이 바뀔 때마다 주문 처리 로직도 함께 수정해야 한다. 즉, 핵심 로직이 기술 세부사항에 끌려다니는 구조가 된다.
DIP는 이처럼 고수준 모듈이 저수준 모듈에 종속되는 것을 방지하고, 고수준 모듈은 역할만 알면 동작할 수 있는 구조를 지향한다.
- 💡 고수준과 저수준 모듈은 추상화에 의존해야 한다.
이 원칙을 실현하기 위해서는 고수준과 저수준 모듈이 서로를 직접 참조하지 않도록, 중간에 추상화 계층을 두어야 한다. 이 추상화는 일반적으로 인터페이스나 추상 클래스의 형태로 정의된다.
예를 들어 Notifier 인터페이스를 정의하고 이메일, SMS, 푸시 알림 전송 클래스들이 이를 구현한다면, 고수준 모듈은 구현체의 존재를 모른 채 인터페이스만 호출하면 된다.
여기서 중요한 점은 추상화를 누가 주도하는가이다. 추상화는 구현이 아니라, 고수준 모듈의 요구사항을 기준으로 설계되어야 한다. 즉, 알림 전송 방식이 아니라 "어떤 메시지를 언제 보낼지"에 대한 계약은 비즈니스 로직이 주도적으로 정의해야 하며, 각 구현체는 이 계약에 자신을 맞춰야 한다.
- 💡 추상화는 구체적인 것에 의존해서는 안 된다.
추상 계층이 특정 구현 방식에 최적화되어 있다면, DIP는 형식적인 수준에 그치게 된다. 예를 들어 Notifier 인터페이스가 이메일 전송에만 적합하도록 정의되어 있다면, 새로운 알림 방식(SMS, 푸시 알림 전송)을 추가하기 어려워지고, 결국 고수준 모듈이 구현체 변경에 취약해지게 된다.
따라서 추상화는 고수준 모듈의 요구사항에 의해 정의되어야 하며, 특정 구현체에 맞춰 설계되어서는 안된다.
- 💡 구체적인 것이 추상화에 의존해야 한다.
DIP의 핵심은 의존 방향을 뒤집는 것이다. 기존 구조에서는 비즈니스 로직이 구현체에 의존하지만, DIP 구조에서는 구현체가 추상화에 맞춰 동작한다. 이로 인해 고수준 모듈은 오직 역할만 알면 되고, 세부 구현은 그 역할에 맞춰 동작한다.
따라서, DIP 원칙을 적용한 시스템은 다음과 같은 구조를 가진다.
- 고수준 모듈은 구현체가 아니라 역할에만 의존한다.
- 저수준 모듈은 고수준 모듈이 정의한 추상화에 맞춰 자신을 구현한다.
- 결국 시스템은 변화에 강하고, 테스트가 가능한 구조가 된다.
2-2. 현실 비유로 이해하는 DIP
이론적 개념을 보다 쉽게 이해하기 위해 실생활의 비유를 들어본다. 대표적인 예는 전자기기, 어댑터, 콘센트의 관계이다.
전 세계는 전압과 콘센트 규격이 제각각이다. 🇺🇸 미국은 110V, 🇰🇷 한국은 220V를 사용하며 콘센트 모양도 다르다. 그럼에도 불구하고 📱스마트폰이나 💻 노트북은 다양한 국가에서 문제없이 동작한다. 그 이유는 바로 어댑터가 있기 때문이다.
어댑터는 콘센트에서 공급되는 전기를 전자기기가 사용할 수 있는 전압과 형태로 변환해준다. 덕분에 전자기기는 외부 환경이 어떤 형태이든 신경 쓸 필요 없이, 어댑터를 통해 일정한 조건에서 안정적으로 작동할 수 있다.
이 구조를 DIP의 관점에서 바라보면 다음과 같다.
- 전자기기는 전원을 공급받아 동작하는 대상이며, 내부적으로는 자신이 어떤 전압이나 어떤 국가의 콘센트에 연결되는지는 전혀 알지 못한다. 오직 "적절한 전원이 공급되는가"에만 관심이 있다.
- 어댑터는 콘센트로부터 받은 전기를 전자기기에 맞게 변환하는 역할을 한다. 전자기기와 콘센트 사이의 차이를 추상화하고 연결하는 매개체다.
- 콘센트는 전기를 공급하는 물리적 실체로, 어떤 기기가 연결되었는지에 관계없이 항상 일정한 방식으로 전원을 제공한다.
이 관계를 애플리케이션 구조에 대응시키면 다음과 같다.
- 전자기기 : 고수준 모듈 = 비즈니스 로직 (예: 주문 처리 로직)
- 어댑터 : 추상화 계층 = Notifier 인터페이스
- 콘센트 : 저수준 모듈 = EmailNotifier, SmsNotifier 등의 구현 클래스
전자기기가 콘센트에 직접 의존하게 되면, 특정 전압이나 규격에만 대응할 수 있게 되어 유연성이 떨어진다. 그러나 어댑터를 중간에 두면, 전자기기는 다양한 전원 환경에 독립적으로 대응할 수 있게 된다.
애플리케이션 설계에서도 마찬가지다. 비즈니스 로직은 알림을 전송해야 할 수 있지만, 그것이 이메일인지 SMS인지, 혹은 어떤 방식으로 전송되는지는 알 필요가 없다. 그저 Notifier 라는 추상화된 인터페이스를 호출하면 된다. 실제 메시지를 전송하는 역할은 EmailNotifier, SmsNotifier와 같은 저수준 구현 클래스가 담당한다.
이처럼 고수준 모듈은 구체적인 구현이 아니라 인터페이스에 의존하게 된다. 반대로 저수준 모듈은 고수준 로직과 분리되어 독립적으로 개발될 수 있으며, 인터페이스를 통해 연결된다. 결과적으로 시스템은 더 유연하고 확장 가능해진다.

3. DIP 위반 예제와 리팩토링
이번 장에서 DIP가 실제 코드에 어떻게 적용될 수 있는지를 비교를 통해 구체적으로 살펴본다. 먼저 DIP가 적용되지 않은 구조를 예로 들어 어떤 문제가 발생하는지를 분석한 후, 이를 DIP 기반 구조로 리팩토링하여 어떤 점이 개선되는지를 단계적으로 설명한다.
3-1. 초보 개발자의 DIP를 무시한 구조
개발 초기에 요구사항이 단순한 경우 구현 세부사항을 비즈니스 로직에 직접 연결하는 방식이 흔히 선택된다. 이는 복잡한 추상화 없이 빠르게 작동하는 결과를 얻을 수 있다는 점에서 직관적으로 보이며, 특히 경험이 부족한 개발자에게는 설계보다 실행이 우선이 되는 경우가 많다.
예를 들어, 주문 처리 시스템에서 이메일로 알림을 보내는 기능이 필요하다고 하자. 초보 개발자는 다음과 같이 직접 EmailNotifier를 생성하고 이를 OrderService 내부에서 바로 사용하는 구조를 선택할 수 있다.
public class EmailNotifier {
public void send(String message) {
System.out.println("이메일 전송: " + message);
}
}
public class OrderService {
private final EmailNotifier notifier;
public OrderService() {
this.notifier = new EmailNotifier(); // 구체 구현에 직접 의존
}
public void processOrder() {
System.out.println("주문 처리 로직 수행...");
notifier.send("주문이 완료되었습니다.");
}
}
이러한 방식은 기능이 단순하고 변화가 없는 경우에는 큰 문제가 되지 않는다. 그러나 이 구조는 다음과 같은 문제를 내포하고 있다.
- ❌ 변경에 닫혀 있음
OrderService는 EmailNotifier라는 구체 클래스에 강하게 결합되어 있다. 만약 향후 다른 알림 방식이 추가되거나 교체되어야 한다면, OrderService의 코드를 직접 수정해야 하며, 이는 개방-폐쇄 원칙(OCP, Open-Closed Principle)을 위반한다.
- ❌ 테스트의 어려움
외부 시스템과의 연동이 포함된 EmailNotifier를 테스트 환경에서 대체하거나 가짜 객체를 가지고 테스트하기 어렵다. 테스트 목적에 맞는 유연한 의존성 주입이 불가능하다.
- ❌ 역할 분리 실패
알림 방식이라는 별도의 관심사를 OrderService 내부에 직접 다루고 있기 때문에, 클래스 간의 책임이 명확히 분리되어 있지 않다. 이로 인해 시스템이 확장될수록 구조가 복잡해지고 변경에 취약한 코드가 된다.
이처럼 구체 구현에 직접 의존하는 구조는 단기적인 구현 효율성을 제공할 수 있지만, 확장성과 유지보수성을 고려할 때 근본적인 설계 개선이 요구된다.
3-2. 능숙한 개발자의 DIP 적용한 설계 개선
능숙한 개발자는 초보 개발자가 설계한 구조가 확장성과 유지보수 측면에서 취약하다는 사실을 인지하고, 이를 해결하기 위해 DIP를 적용하여 구조를 리팩토링한다. 이 원칙의 핵심은 고수준 모듈(비즈니스 로직)이 저수준 모듈(구현 세부사항)에 의존하지 않고, 공통된 추상화에 의존하도록 방향을 전환하는 것이다.
이제 단계적으로 리팩토링 과정을 살펴보자.
- 1️⃣ 단계 : 알림 수단의 추상화 도입
먼저 알림 방식에 대한 추상화를 정의한다. 이를 통해 알림의 구체적인 구현이 아닌, 알림이라는 개념 자체에 의존할 수 있는 기반을 마련한다.
public interface Notifier {
void send(String message);
}
- 2️⃣ 단계 : 각 알림 방식의 구현
이제 이메일 및 SMS 전송과 같은 구체적인 알림 방식은 Notifier 인터페이스를 구현하는 별도의 클래스로 정의된다. 새로운 알림 방식이 필요한 경우 이 인터페이스만 구현하면 된다.
public class EmailNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("이메일 전송: " + message);
}
}
public class SmsNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("SMS 전송: " + message);
}
}
- 3️⃣ 단계 : 비즈니스 로직 클래스의 추상화 의존
기존에 EmailNotifier에 직접 의존하던 OrderService는 이제 Notifier라는 추상 타입에 의존하도록 변경된다. 이를 통해 비즈니스 로직은 구체 구현으로부터 완전히 분리된다.
public class OrderService {
private final Notifier notifier;
public OrderService(Notifier notifier) {
this.notifier = notifier; // 추상화에 의존
}
public void processOrder() {
System.out.println("주문 처리 로직 수행...");
notifier.send("주문이 완료되었습니다.");
}
}
- 4️⃣ 단계 : 실행부 코드
실행 시점에 원하는 구현체를 선택하여 OrderService에 주입한다. 이로써 구현에 대한 제어권은 클라이언트로 이동하고, 결합도는 낮아진다.
public class Main {
public static void main(String[] args) {
Notifier notifier = new EmailNotifier(); // 또는 SmsNotifier
OrderService service = new OrderService(notifier);
service.processOrder();
}
}
이로써 비즈니스 로직은 기술 세부사항으로부터 완전히 분리되고, 의존성의 방향은 역전되어 구현이 아닌 추상화로 향하게 된다. 이렇게 리팩토링된 구조는 다음과 같은 이점을 가진다.
- ✅ 변화에 강한 구조
시간이 지남에 따라 새로운 알람 채널이 추가되더라도 OrderService는 전혀 변경되지 않는다. Notifier 인터페이스를 구현한 새로운 클래스만 정의하면 된다.
- ✅ 테스트 용이성 향상
테스트 환경에서는 실제로 메시지를 전송하지 않는 MockNotifier를 주입함으로써, 알림 기능을 제외한 순수한 비즈니스 로직만 독립적으로 검증할 수 있다.
- ✅ 책임 분리로 인한 명확한 구조
각 클래스는 자신이 맡은 역할만 수행하므로, SOLID 원칙 중 단일 책임 원칙(SRP, Single Responsibility Principle)을 만족하며 구조가 명확하고 유지보수가 쉬워진다.
- ✅ 유지보수성과 확장성 향상
알림 방식의 변경이나 추가가 빈번한 시스템에서도 비즈니스 로직은 안정적으로 유지된다. 이러한 구조는 변화에 유연하게 대응할 수 있으며, 결과적으로 유지보수 비용을 줄이고 시스템의 품질을 향상시킨다.
4.Spring에서 DIP 실현하기 - IoC & DI
앞서 순수 자바 코드로 DIP를 적용한 구조를 살펴봤다. 하지만 실무에서는 수많은 객체 간의 의존 관계를 직접 구성하고 관리하는 것이 비효율적이며, 오류 가능성도 높다.
이 문제를 해결해 주는 것이 Spring 프레임워크이다. Spring은 객체의 생성, 연결, 주입을 자동으로 처리하는 기능을 제공함으로써, 개발자가 DIP를 보다 손쉽게 실현할 수 있도록 돕는다.
4-1. Spring 기반의 IoC 및 DI 개념
- 제어의 역전 (IoC, Inversion of Control)
일반적인 자바 코드에서는 객체를 new 키워드로 직접 생성하고, 필요한 의존 객체도 클래스 내부에서 명시적으로 생성한다. 즉, 객체 생성과 조립의 제어권이 클래스 내부에 있다.
Spring에서는 이 제어권을 개발자가 아닌 컨테이너(Spring Context)로 넘긴다. 개발자는 구성만 정의하고, 실제 생성과 연결은 Spring이 수행한다. 이것이 제어의 역전이다.
- 의존성 주입 (DI, Dependency Injection)
DI는 IoC를 구현하는 방법 중 하나이다. Spring은 객체 간 의존성을 외부에서 주입함으로써, 클래스가 직접 구현체를 생성하지 않고도 동작할 수 있게 만든다.
즉, 비즈니스 로직은 "무엇을 할 것인가?"에만 집중하고, "어떻게 할 것인가?"는 Spring에서 관리한다. 이 구조는 자연스럽게 DIP의 설계 방향과 일치한다.
※ 참고 : DIP vs DI
- DIP는 설계 원칙으로 "고수준 모듈이 추상화에 의존하도록 하자."
- DI는 구현 방법으로 "그 추상화를 Spring이 대신 주입해준다."
- 즉, DI는 DIP 실현을 도울 수 있는 도구일 뿐이며, 개발자가 추상화에 의존하도록 설계를 수행해야 DIP가 적용될 수 있다.
요약하자면, IoC는 객체 생성과 제어의 주체가 개발자가 아닌 프레임워크로 넘어간 것을 의미하고, DI는 IoC 구조 안에서 의존 객체를 외부에서 주입받는 방식을 말하며, 이를 통해 DIP가 자연스럽게 적용된다. 따라서, Spring은 IoC 컨테이너를 통해 DI를 지원하고, 개발자는 추상화에만 의존하는 유연하고 테스트 가능한 코드를 작성할 수 있게 된다.
4-2. Spring 기반 DIP 구현 예제
앞서 순수 자바 기반으로 DIP를 적용한 알림 시스템 예제를 Spring 기반으로 변경하여, 실제 환경에서 DIP가 어떻게 적용되는지 살펴본다.
- 1️⃣ 단계 : 추상화 계층 정의
알림 기능을 나타내는 인터페이스는 그대로 유지된다. 이 인터페이스는 비즈니스 로직이 알림 방식의 구체 구현에 의존하지 않도록 한다.
public interface Notifier {
void send(String message);
}
- 2️⃣ 단계 : 구현체 정의 및 등록 (@Component)
각 알림 방식은 Notifier 인터페이스를 구현하고, @Component를 통해 Spring Bean으로 등록된다. Spring은 애플리케이션 실행 시점에 이들을 스캔하여 컨테이너에 등록한다.
@Component
public class EmailNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("이메일 전송: " + message);
}
}
@Component
public class SmsNotifier implements Notifier {
@Override
public void send(String message) {
System.out.println("SMS 전송: " + message);
}
}
- 3️⃣ 단계 : 비즈니스 로직 정의 및 주입 (@Service, @Autowired)
비즈니스 로직을 담당하는 OrderService 클래스는 Notifier 인터페이스에만 의존한다., Spring이 DI를 통해 적절한 구현체를 주입해준다. @Service는 이 클래스가 비즈니스 로직을 담당하는 구성요소임을 나타낸다. @Autowired는 Spring에게 Notifier 타입에 맞는 Bean을 찾아서 넣어달라 요청한다.
@Service
public class OrderService {
private final Notifier notifier;
@Autowired
public OrderService(Notifier notifier) {
this.notifier = notifier; // DIP: 추상화에 의존, 구현은 주입됨
}
public void processOrder() {
System.out.println("주문 처리 로직 수행...");
notifier.send("주문이 완료되었습니다.");
}
}
- 4️⃣ 단계 : 실행부 코드 (@SpringBootApplication 기반)
실행 클래스는 일반적으로 Spring Boot 애플리케이셔 구조를 따른다. Spring은 애플리케이션 기동 시 Notifier 구현체를 주입하여 OrderService를 자동으로 초기화한다.
@SpringBootApplication
public class NotificationApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(NotificationApplication.class, args);
OrderService service = context.getBean(OrderService.class);
service.processOrder();
}
}
- ※ 참고 : Spring에서 구현체가 여러 개인 경우의 처리
Notifier를 구현한 Bean이 여러 개 있을 경우, Spring은 어떤 구현을 주입해야 할지 결정하지 못한다. 이때는 다음 중 하나의 방법으로 해결할 수 있다.
- 방법 1️⃣ : 기본 구현 지정 (@Primary)
@Component
@Primary
public class EmailNotifier implements Notifier {
// 기본 구현으로 지정
}
- 방법 2️⃣ : 명시적 선택 (@Qualifier)
@Service
public class OrderService {
private final Notifier notifier;
@Autowired
public OrderService(@Qualifier("smsNotifier") Notifier notifier) {
this.notifier = notifier;
}
...
}
정리하면 Spring을 활용하면 다음과 같은 구조를 자연스럽게 구현할 수 있다.
- 인터페이스 : 고수준 모듈이 의존할 추상화
- 구현 클래스 : 저수준 모듈
- Service 클래스 : DIP 적용 대상, 추상화에 의존하도록 설계
- DI + IoC : Spring이 의존성 연결을 자동으로 주입
이렇게 구성된 시스템에서 개발자는 역할과 책임만 설계하면 되고, 구현체의 생성과 연결은 Spring이 알아서 처리한다. 이것이 Spring이 DIP를 실질적으로 구현하는 방식이다.
5. 마무리 - 테스트 가능성과 유지보수성을 높이는 설계 원칙 DIP
지금까지의 내용을 정리하면 다음과 같다.
의존성 역전 원칙(DIP, Dependency Inversion Principle)은 OOP 중에서도 구조적인 유연성과 테스트 가능성, 유지보수성에 직접적인 영향을 미치는 원칙이다. 이 원칙의 본질은 간단하다. 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 대신 양쪽 모두 추상화에 의존해야 한다. 그리고 더 나아가서는 구체적인 구현이 추상화에 자신을 맞춰야 한다는 것이다.
이러한 구조가 갖춰지면 시스템의 중심에는 바뀌지 않는 비즈니스 규칙이 자리하게 된다. 예를 들어 알림 기능을 이메일에서 SMS로 바꾸는 상황에서도, 알림을 언제, 왜, 무엇을 보내야 하는지에 대한 비즈니스 로직은 전혀 손대지 않아도 된다. 단지 구현체만 교체하면 되기 때문이다.
DIP는 결국 역할과 구현을 분리하는 설계 사고방식이다. 비즈니스 로직은 해야 할 일만 알고 있고, 그 일이 어떻게 수행되는가는 외부에 위임한다. 이때 추상화는 일종의 계약서 역할을 하며, 구현체는 그 계약을 지키는 형태로 설계된다. 결과적으로 코드 구조는 유연해지고, 변화에 강한 시스템으로 발전하게 된다.
그렇다고 해서 DIP가 모든 상황에서 무조건 적용되어야 하는 것은 아니다. 변화 가능성이 낮고, 구현이 고정되어 있으며, 대체할 책임이 없는 경우에는 굳이 추상화를 도입하지 않아도 된다. 오히려 불필요한 인터페이스 도입은 설계를 복잡하게 만들고, 개발 생산성을 해칠 수도 있다. 중요한 것은 다음과 같은 질문을 스스로 던지는 것이다. 만약 '그렇다'고 답할 수 있다면, 지금이 DIP를 적용할 시간이다.
- 이 구현은 나중에 바뀔 가능성이 있는가?
- 이 역할은 다른 방식으로 대체될 수 있는가?
실무에서는 DIP 설계를 손쉽게 실현할 수 있는 도구가 필요하다. 바로 그 역할을 Spring 프레임워크가 담당한다. Spring은 IoC 컨테이너를 통해 객체의 생성과 주입을 자동으로 처리하고, 개발자는 추상화에만 의존된 구조를 설계하면 된다. 이렇게 설계와 구현의 책임이 분리되면, 개발자는 변화에 유연하게 대응할 수 있고, 시스템은 점점 더 단단해진다.
변화할 수 있는 것은 바깥으로 밀어내고, 변하지 않는 핵심은 안쪽에서 안정적으로 유지하는 구조를 만드는 것이 DIP가 지향하는 방향이다.
'설계 > 설계 원칙' 카테고리의 다른 글
| SOLID의 ISP : 역할 중심 설계를 도와주는 인터페이스 분리 원칙 (0) | 2025.05.07 |
|---|---|
| SOLID의 LSP : 왜 타입만 맞으면 안 되는가? 설계 오류를 막는 리스코프 치환 원칙 (0) | 2025.05.02 |
| SOLID의 OCP : 조건문 없이 유연하게 확장하는 개방-폐쇄 원칙 (0) | 2025.05.01 |
| SOLID의 SRP : 클래스 책임 분리로 복잡도를 낮추는 단일 책임 원칙 (0) | 2025.04.29 |
