의존성 역전 원칙 이해하기: 변수 하드코딩에서 함수 하드코딩까지#
“의존성 역전”이라는 용어를 처음 들었을 때 많은 개발자들이 혼란스러워합니다. 하지만 사실 우리가 이미 잘 알고 있는 개념과 정확히 같은 패턴입니다. 바로 하드코딩을 피하는 것입니다.
변수 하드코딩 vs 설정파일: 우리가 이미 아는 패턴#
나쁜 예시: 변수 하드코딩#
1
2
3
4
5
6
7
| function 파일읽기() {
return readFile("/Users/myname/project/data.txt"); // 하드코딩!
}
function 데이터베이스연결() {
return connect("mysql://localhost:3306/mydb"); // 하드코딩!
}
|
이렇게 하면 무엇이 문제일까요?
- 개발/운영 환경이 다르면? 코드를 수정해야 합니다
- 다른 사용자가 다른 경로를 사용하면? 코드를 수정해야 합니다
- MySQL에서 PostgreSQL로 바뀌면? 코드를 수정해야 합니다
좋은 예시: 설정파일 사용#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // config.json
{
"filePath": "/Users/myname/project/data.txt",
"databaseUrl": "mysql://localhost:3306/mydb"
}
// 유연한 코드
function 파일읽기(경로) {
return readFile(경로); // 유연함!
}
function 데이터베이스연결(url) {
return connect(url); // 유연함!
}
// 사용할 때
const config = readConfig();
파일읽기(config.filePath);
데이터베이스연결(config.databaseUrl);
|
이제 코드를 수정하지 않고도 설정파일만 바꾸면 됩니다!
함수 하드코딩: 같은 문제, 다른 영역#
그런데 우리는 변수는 하드코딩하지 않으면서, 함수는 하드코딩하고 있었습니다.
나쁜 예시: 함수 하드코딩#
1
2
3
4
5
| function 주문처리(주문정보) {
// 함수가 하드코딩됨!
이메일발송(주문정보.email);
MySQL저장(주문정보);
}
|
이렇게 하면 무엇이 문제일까요?
- 이메일 대신 SMS로 바꾸고 싶으면? 코드를 수정해야 합니다
- MySQL 대신 MongoDB로 바꾸고 싶으면? 코드를 수정해야 합니다
- VIP 고객은 이메일, 일반 고객은 SMS를 보내고 싶으면? 코드를 수정해야 합니다
좋은 예시: 함수를 변수처럼 전달#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| function 주문처리(주문정보, 알림함수, 저장함수) {
알림함수(주문정보.contact); // 유연함!
저장함수(주문정보); // 유연함!
}
// 다양한 구현체들
function 이메일발송(연락처) {
console.log(`${연락처}로 이메일 발송`);
}
function SMS발송(연락처) {
console.log(`${연락처}로 SMS 발송`);
}
function MySQL저장(데이터) {
console.log(`MySQL에 저장: ${데이터.id}`);
}
function MongoDB저장(데이터) {
console.log(`MongoDB에 저장: ${데이터.id}`);
}
// 사용할 때 - 마치 설정파일처럼!
주문처리(주문, 이메일발송, MySQL저장); // 조합1
주문처리(주문, SMS발송, MongoDB저장); // 조합2
|
의존성 역전 = 함수 하드코딩을 피하는 것#
결국 의존성 역전이란 함수를 변수처럼 취급하는 것입니다.
전통적인 의존성#
1
| 고수준 함수 → 구체적인 저수준 함수 (하드코딩)
|
의존성 역전#
1
| 고수준 함수 → 함수 인터페이스 ← 구체적인 구현 함수
|
마치 이렇게 바뀌는 것과 같습니다:
변수 영역에서#
1
| 함수 → 하드코딩된 값 => 함수 → 변수 → 설정파일의 값
|
함수 영역에서#
1
| 함수 → 하드코딩된 함수 => 함수 → 함수변수 → 실제 구현 함수
|
껍데기와 내용의 분리#
이 패턴의 핵심은 “껍데기”와 “내용”을 분리하는 것입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // 껍데기 (계약/인터페이스) - 어떤 모양인지만 정의
const 알림인터페이스 = {
send: (메시지) => { /* 어떻게든 알림을 보낸다 */ }
}
// 내용 (구현체) - 실제로 어떻게 할지 정의
const 이메일알림 = {
send: (메시지) => console.log(`이메일: ${메시지}`)
}
const SMS알림 = {
send: (메시지) => console.log(`SMS: ${메시지}`)
}
// 주문처리는 껍데기만 알면 됨
function 주문처리(알림기) {
알림기.send("주문완료!"); // 내용은 몰라도 됨
}
|
파이프라인 사고와의 연결#
파이프라인에서 각 파이프의 연결부 규격은 고정하되, 파이프 내부 구현은 교체 가능하게 만드는 것과 정확히 같습니다.
1
2
3
4
| [주문입력] → [주문처리] → [알림파이프] → [저장파이프]
↑ ↑
이메일/SMS MySQL/MongoDB
교체 가능 교체 가능
|
Factory 패턴: 설정 기반 조립#
마지막으로, 설정파일과 Factory 패턴을 결합하면 완전히 유연한 시스템을 만들 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // config.json
{
"notification": "email",
"database": "mysql"
}
// Factory - 설정에 따라 함수 조합
function 주문처리Factory(config) {
const 알림함수 = config.notification === 'email' ? 이메일발송 : SMS발송;
const 저장함수 = config.database === 'mysql' ? MySQL저장 : MongoDB저장;
return (주문) => 주문처리(주문, 알림함수, 저장함수);
}
// 사용
const config = readConfig();
const 주문처리기 = 주문처리Factory(config);
주문처리기(주문정보);
|
마무리#
의존성 역전은 새로운 개념이 아닙니다. 우리가 이미 변수 영역에서 하고 있던 **“하드코딩을 피하는 좋은 습관”**을 함수 영역으로 확장한 것뿐입니다.
- 변수 하드코딩 → 설정파일: 데이터의 유연성
- 함수 하드코딩 → 의존성 주입: 동작의 유연성
이렇게 이해하면 의존성 역전이 왜 필요하고, 어떻게 적용해야 하는지 훨씬 명확해집니다. 결국 더 유연하고 재사용 가능한 코드를 만들기 위한, 우리가 이미 알고 있는 패턴의 확장인 셈이죠.