◆ TURNING · SPRING

new 의 함정

강한 결합을 직접 느껴본다

학습 목표

  • new 가 만드는 강한 결합 을 코드로 본다
  • 부품 교체 시 코드가 어디까지 부서지는지 체감
  • 다음 차시 IoC / DI 의 동기를 명확히 한다

⚠️ 평범해 보이는 코드

자연스러운 OrderService

public class OrderService {
    private MailSender mail = new GmailSender();   // ⭐

    public void order() {
        // ... 주문 처리
        mail.send("주문 완료 메일");
    }
}

아무 문제 없어 보입니다. 학생이 자바를 배운 직후 자연스럽게 짤 코드 — new 로 직접 만들기.

요청서 도착

기획자: "이메일 자꾸 스팸으로 빠져요. 카카오톡 알림으로 바꿉시다." 개발자: "쉽죠 — 한 줄만 바꾸면 되니까..."

변경 시도 ① — 단순 교체


public class OrderService {
    private KakaoSender mail = new KakaoSender();  // 변경
    //      ^^^^^^^^^^^

    public void order() {
        mail.send("주문 완료 메일");   // 메서드 이름이 send 가 맞나?
    }
}
알고 보니

KakaoSender 의 메서드 이름이 send 가 아니라 notify(String userId, String msg) — 시그니처도 다름.

변경 시도 ② — 줄줄이 수정


public class OrderService {
    private KakaoSender messenger = new KakaoSender();
    //      ^^^^^^^^^^^   ^^^^^^^^^   ^^^^^^^^^^^

    public void order() {
        messenger.notify("user123", "주문 완료");
        //        ^^^^^^   ^^^^^^^^^^^   ^^^^^^^^^^
    }
}

👉 한 클래스에서만 5 줄 수정. 같은 패턴이 5 곳에 있다면 — 5 × 5 = 25 줄 수정.

도미노 효과

① OrderService 의 mail 필드 이름 변경 ↓ ② OrderService 의 호출 코드 변경 ↓ ③ 같은 패턴인 CartService 도 변경 ↓ ④ NotificationService 도 변경 ↓ ⑤ 단위 테스트들 수정 ↓ ⑥ 커밋·검토 — 한 줄 요청이 50 줄 변경

🛠️ 강한 결합의 정체

「내가 직접 만든다」 의 대가

클래스 내부에서 new ConcreteClass() 로 객체를 직접 생성하면 — 그 클래스는 해당 구체 클래스에 꽉 묶여 있게 됩니다.

OrderService ──new──→ GmailSender ▲ 꽉 묶임 │ 부품 교체하려면 OrderService 자체를 수정해야 함 → OCP (개방-폐쇄 원칙) 위배

비유 — 일체형 가전 vs 모듈식

일체형 가전 (강한 결합) ┌─────────────────────────┐ │ 메인보드 스피커 배터리│ │ 전부 한 보드에 │ └─────────────────────────┘ 메인보드 고장 → 통째로 버려야 함 모듈식 PC (느슨한 결합) [메인보드] + [GPU] + [RAM] + [SSD] 각 부품 따로 교체 가능 → 한 부품 고장 = 그 부품만 교체

본질적 문제 — OCP 위배

OCP (개방-폐쇄 원칙): "확장에는 열려있고, 수정에는 닫혀있어야 한다."

  • 새 부품(KakaoSender) 추가 = 「확장」 (OK)
  • OrderService 자체 수정 = 「수정」 (OCP 위배)
  • 강한 결합은 「수정 없이 확장」 을 불가능하게 함

다음 차시의 약속

다음 두 차시 (IoC + DI) 에서 다음 패턴을 배웁니다 — 미리 살짝:


public class OrderService {
    private MessageSender sender;     // 인터페이스에 의존

    public OrderService(MessageSender sender) {  // 외부에서 받음
        this.sender = sender;
    }

    public void order() {
        sender.send("주문 완료");
    }
}
  • 구체 클래스(GmailSender) 가 사라짐 → 인터페이스만 의존
  • 객체 생성도 외부로 넘김
  • 부품 교체 시 OrderService 0 줄 수정

실험 — 직접 깨뜨려보기

  1. v1 코드 작성: GmailSender 를 OrderService 에 new
  2. 실행 — 잘 동작
  3. KakaoSender 클래스 추가
  4. OrderService 의 GmailSender → KakaoSender 변경 시도
  5. 변경해야 할 줄 수 직접 세보기
  6. 「부품 갈아끼우기 어려운 상태」 의 답답함 체감

🔄 Before / After

전 차시 끝

「new 잘 쓰면 되는 거 아닌가?」 의 막연한 자세.

이번 차시 끝

부품 교체 시 코드가 어디까지 부서지는지 손으로 느꼈다. 다음 도구의 동기가 명확.

이번 차시의 데이터 흐름

OrderService
GmailSender
(꽉 묶임)
두 박스가 「new」로 강하게 묶임 — 다음 차시에 풀어냄

정리

오늘 들고 가는 것

  • new = 클래스 내부에서 직접 만들기 = 강한 결합
  • 부품 교체 시 클래스 자체 수정 → 도미노 효과
  • OCP (개방-폐쇄 원칙) 위배
  • 다음 차시 — IoC/DI 가 이 함정을 푼다