의존관계 역전 원칙(DIP)과 추상화
2분 읽기
의존관계 역전 원칙이 왜 필요한가요?링크를 제목에 복사
구체화가 아니라 추상화에 의존해야 한다.
즉, 구현 클래스(구현체)가 아니라 인터페이스(역할)에 의존해야 한다.
의존관계 역전 원칙은 유연하지 못한 코드(Code Rigidity)와 모듈 간의 강한 결합(Tight Coupling of Modules)을 피하기 위해 고안되었습니다.
핵심 아이디어는 다음 두 가지입니다.
- 고수준 모듈은 저수준 모듈의 구현에 직접 의존해서는 안 됩니다. 저수준 모듈이
interface와 같은 고수준 모듈에서 정의한 추상 타입에 의존해야 합니다. - 추상화는 세부 사항에 의존해서는 안 되며, 세부 사항(concrete implementations)은 추상화에 의존해야 합니다.
고수준 모듈과 저수준 모듈이란?링크를 제목에 복사
고수준 모듈(High-level Module): 의미 있는 단일 기능을 제공하는 모듈저수준 모듈(Low-level Module): 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현
코드로 살펴보면 어떻게 달라지나요?링크를 제목에 복사
의존관계 역전 원칙을 위반하는, 즉 모듈 간의 강한 결합을 가진 코드를 개선하는 예제를 살펴보겠습니다.
As-Is링크를 제목에 복사
class LightBulb {
turnOn() {
console.log('Light bulb turned on.');
}
}
class Switch {
constructor() {
this.bulb = new LightBulb();
}
operate() {
this.bulb.turnOn();
}
}
const switchButton = new Switch();
switchButton.operate();Switch 클래스가 LightBulb 클래스를 직접 의존하고 있어 모듈 간의 강한 결합이 발생합니다.
이 상태에서 LightBulb를 다른 모듈로 교체해야 한다면, Switch 클래스의 내부 로직도 함께 변경해야 한다는 문제가 생깁니다.
그렇다면 우리가 원하는 것은 무엇일까요? 저수준 모듈(LightBulb)이 변경되더라도 고수준 모듈(Switch)은 변경되지 않는 구조입니다.
의존관계 역전 원칙(DIP)을 적용하면 다음과 같이 개선할 수 있습니다.
To-Be링크를 제목에 복사
// 추상화: 어떤 장치든 turnOn을 구현하면 Switch가 제어할 수 있습니다
class Switchable {
turnOn() {}
}
class LightBulb extends Switchable {
turnOn() {
console.log('Light bulb turned on.');
}
}
class Switch {
constructor(device) {
this.device = device;
}
operate() {
this.device.turnOn();
}
}
const lightBulb = new LightBulb();
const switchButton = new Switch(lightBulb);
switchButton.operate();의존 관계를 맺을 때는 자신보다 변화하기 쉬운 것을 의존해서는 안 되고, 거의 변화가 없는 개념에 의존해야 합니다.
이제 Switch 클래스는 LightBulb 대신 추상화된 Switchable 인터페이스에 의존합니다.
결국 두 모듈 간의 느슨한 결합(Loose Coupling)이 달성되어, 이후 LightBulb가 아닌 다른 장치로 교체가 필요하더라도 Switch 내부 로직은 전혀 변경할 필요가 없습니다.
추상화란 무엇인가요?링크를 제목에 복사
복잡한 내부 프로세스의 로직을 감추고, 간결하고 직관적인
인터페이스를 제공하는 것
프로그래밍에서 추상화(Abstraction)는 다루기 어려운 복잡한 대상을 의도적으로 감춰 사용자 또는 다른 개발자에게 명료하고 간결한 인터페이스를 제공하는 중요한 도구입니다.
잘 추상화된 기능이나 모듈은 구체적인 구현 세부 사항을 몰라도 쉽게 활용할 수 있게 됩니다.
DIP와 추상화는 이런 관계입니다. DIP는 어디에 의존할 것인지의 방향을 정하고, 추상화는 그 의존의 대상을 만드는 행위입니다. 앞선 예제에서 Switchable이 바로 그 대상이었습니다.
추상화는 어떤 이점을 가져오나요?링크를 제목에 복사
추상화는 단순한 가독성 향상 이상의 목적을 달성합니다.
복잡성을 숨김으로써 코드의 가독성을 향상시키고, 재사용 및 유지보수성을 높여 개발 과정을 더욱 효율적으로 만들어줍니다.
또한 모듈화를 통해 독립성을 높여 특정 모듈에서 발생한 문제가 전체 시스템에 영향을 미치는 것도 막을 수 있습니다.
정리하면, 추상화가 가져다주는 이점은 다음과 같습니다.
- 단순화
- 재사용성 증가
- 유연성 및 확장성 (유지보수)
- 시스템 분리 및 모듈화
- 독립성 증가
얼마나 추상화하는 것이 적절한가요?링크를 제목에 복사
추상화에 정답은 없지만, 상황과 목적에 따라 다르게 적용되어야 합니다.
상황에 따라 필요한 만큼만 노출시키기 위해 추상화 수준(level of abstraction) 을 조정하는 과정이 필요합니다.
잘못된 추상화보다는 중복이 낫다.는 말처럼, 과도한 추상화는 오히려 코드에서 나쁜 냄새를 만드는 주범이 될 수 있습니다.
그래서 추상화에 앞서 항상 스스로에게 물어볼 필요가 있습니다. 지금 이 추상화가 성급한 추상화는 아닌지, 상황과 목적에 따라 적절하게 조정되었는지를요.
정리하면링크를 제목에 복사
- 고수준 모듈은 저수준 모듈의 구현이 아니라 추상화(인터페이스)에 의존해야 합니다. 구현체가 바뀌어도 상위 모듈은 변경되지 않는 구조가 DIP의 핵심입니다.
- 추상화는 복잡한 구현을 감추고 명료한 인터페이스를 제공합니다. DIP는 어디에 의존할지 방향을 정하고, 추상화는 그 의존의 대상을 만드는 행위입니다.
- 추상화는 많을수록 좋지 않습니다. 과도한 추상화는 오히려 코드를 복잡하게 만들 수 있으므로, 상황과 목적에 따라 적절한 수준을 유지하는 것이 중요합니다.
관련 글
이벤트 기반 아키텍처(Event-Driven Architecture)
이벤트 기반 아키텍처의 개념, 브로커·중재자 토폴로지 비교, Event Notification·ECST·Event Sourcing·CQRS 패턴을 정리합니다.
안정적인 API를 위한 HTTP 멱등성 이해하기
HTTP 메서드별 안전함(Safe)과 멱등성(Idempotency)의 차이를 정리하고, PATCH·POST에서 멱등키를 활용해 중복 요청을 방지하는 방법을 소개합니다.
Stateful vs Stateless
Stateful과 Stateless 아키텍처의 개념과 각각의 장단점을 정리하고, 모든 것을 무상태로 설계할 수 없는 이유를 살펴봅니다.
모든 글