5주차 Unit 6.1 — 인터페이스로 결합도 낮추기

Psj·2026년 5월 27일

F-lab

목록 보기
172/240

Unit 6.1 — 인터페이스로 결합도 낮추기

F-LAB JAVA · 5주차 · Phase 6 · 객체지향 설계 원칙 (OCP & 전략 패턴)
🌱 Phase 6 시작 — 인터페이스 + 합성의 본격 도입


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • ConnectionMaker 인터페이스 도입 의 효과는?
  • 인터페이스에만 의존 한다는 의미는?
  • 외부에서 주입받기 (생성자) 의 방식은?
  • ShipmentDao 가 새 DB 추가에도 변경 안 되는 이유는?
  • 결합도 (coupling) 란?
  • 구현체 결정 책임은 누구에게 인가?
  • DIP (의존 역전 원칙) 와의 연결은?
  • 인터페이스 분리의 효과 는?
  • 상속에서 합성으로의 전환 은?

🎯 핵심 한 문장

ConnectionMaker 인터페이스를 도입하고 ShipmentDao 가 이 인터페이스에만 의존하여 외부에서 구현체를 주입받으면, 어떤 DB 구현체가 와도 ShipmentDao 코드를 변경하지 않아 결합도가 낮아진다.
변하는 부분 (DB 연결) 을 ConnectionMaker 인터페이스로 추상화하고, ShipmentDao 는 구체 구현이 아닌 인터페이스에만 의존 한다.
구체 구현체 (NConnectionMaker, DConnectionMaker) 는 외부에서 생성자로 주입 받으므로, ShipmentDao 는 어떤 구현체가 오는지 모른 채 인터페이스의 메서드만 호출한다.
그 결과 새로운 DB 를 지원하려면 새 ConnectionMaker 구현체를 추가하기만 하면 되고, ShipmentDao 코드는 전혀 변경되지 않는다 (결합도가 낮아짐).
이때 "어떤 구현체를 쓸지 결정하는 책임" 은 ShipmentDao 가 아니라 외부 (조립하는 곳) 로 넘어가며, 이것이 DIP (의존 역전 원칙) 와 이후 IoC/DI 로 발전한다.

비유 — 표준 충전 단자 (USB-C)

인터페이스 = 표준 충전 단자 (USB-C):

전 (전용 충전기 — 강결합):
  - 기기마다 전용 충전기 (상속)
  - 충전기 바꾸면 기기 못 씀

후 (USB-C 표준 — 인터페이스):
  - 기기는 USB-C 단자만 (인터페이스 의존)
  - 어떤 USB-C 충전기든 OK
  - 충전기 = ConnectionMaker 구현체

주입:
  - 충전기를 "꽂음" (외부 주입)
  - 기기는 어떤 충전기인지 모름
  - 단자(인터페이스)만 맞으면 OK

결합도 ↓:
  - 새 충전기 나와도 기기 그대로
  - 기기는 USB-C 만 의존

결정 책임:
  - 어떤 충전기 꽂을지 = 사용자(외부)
  - 기기가 결정 X

→ 인터페이스 도입 = USB-C 표준, 외부 주입, 결합도 ↓, 새 DB 에도 ShipmentDao 불변.


🧭 9개 섹션 로드맵

1. ConnectionMaker 인터페이스 도입
2. 인터페이스에만 의존
3. 외부에서 주입 (생성자)
4. 새 DB에도 변경 안 됨
5. 결합도란
6. 구현체 결정 책임
7. DIP와의 연결
8. 상속에서 합성으로
9. 면접 + 자기 점검

1️⃣ ConnectionMaker 인터페이스 도입

1.1 인터페이스 정의

// ConnectionMaker 인터페이스
public interface ConnectionMaker {
    Connection makeConnection() throws Exception;
}

1.2 변하는 부분 추상화

변하는 부분 추상화:

  DB 연결 (변하는 부분):
    - 인터페이스로 추상화
    - makeConnection()

  → 계약만 정의
  → 구현은 별도

1.3 구현체

// N사 연결 구현
public class NConnectionMaker implements ConnectionMaker {
    public Connection makeConnection() throws Exception {
        Class.forName("com.mysql.jdbc.Driver");
        return DriverManager.getConnection(
            "jdbc:mysql://N-server/ilic", "nuser", "npass");
    }
}

// D사 연결 구현
public class DConnectionMaker implements ConnectionMaker {
    public Connection makeConnection() throws Exception {
        // D사 연결 방식
        return null;
    }
}

1.4 추상클래스 vs 인터페이스

추상클래스 (Phase 4) → 인터페이스 (Phase 6):

추상클래스:
  - 상속 (단일)
  - 강결합

인터페이스:
  - 구현 (다중)
  - 합성 (주입)
  - 느슨

1.5 ILIC 의 맥락

// ConnectionMaker 인터페이스 (변하는 부분)
public interface ConnectionMaker {
    Connection makeConnection() throws Exception;
}

// 고객사별 구현
public class CustomerAConnectionMaker implements ConnectionMaker {
    public Connection makeConnection() throws Exception {
        return DriverManager.getConnection(
            "jdbc:mysql://customerA/ilic", "userA", "passA");
    }
}

public class CustomerBConnectionMaker implements ConnectionMaker {
    public Connection makeConnection() throws Exception {
        return DriverManager.getConnection(
            "jdbc:oracle:thin:@customerB:1521:ilic", "userB", "passB");
    }
}
// 연결 방식이 인터페이스 구현으로

1.6 자기 점검 답변

ConnectionMaker 인터페이스 도입의 효과는?

:
1. 인터페이스:

  • makeConnection 계약
  1. 추상화:

    • 변하는 부분 (연결)
  2. 구현체:

    • N/DConnectionMaker
  3. vs 추상클래스:

    • 다중 구현, 합성

2️⃣ 인터페이스에만 의존

2.1 인터페이스 의존

// ShipmentDao 는 인터페이스에만 의존
public class ShipmentDao {
    private final ConnectionMaker connectionMaker;   // 인터페이스
    
    public void add(Shipment s) throws Exception {
        Connection c = connectionMaker.makeConnection();   // 인터페이스 메서드
        // 구체 구현 모름
    }
}

2.2 구체 모름

구체 모름:

  ShipmentDao:
    - ConnectionMaker (인터페이스) 만 앎
    - NConnectionMaker (구체) 모름

  → 어떤 구현이든 OK

2.3 의존의 방향

의존의 방향:

  ShipmentDao → ConnectionMaker (인터페이스)
                     ↑ 구현
              N/DConnectionMaker

  - ShipmentDao 는 인터페이스만
  - 구체는 인터페이스 구현

2.4 효과

효과:

  - 구체 변경 무관
  - 새 구현 추가 무관
  - ShipmentDao 안정

2.5 ILIC 의 맥락

public class ShipmentDao {
    private final ConnectionMaker connectionMaker;   // 인터페이스 의존
    
    public ShipmentDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;
    }
    
    public void add(Shipment shipment) throws Exception {
        Connection c = connectionMaker.makeConnection();   // 인터페이스만
        PreparedStatement ps = c.prepareStatement(
            "insert into shipments(id, bl_no, weight) values(?, ?, ?)");
        ps.setLong(1, shipment.getId());
        ps.executeUpdate();
        ps.close(); c.close();
    }
    
    public Shipment get(Long id) throws Exception {
        Connection c = connectionMaker.makeConnection();   // 인터페이스만
        // ...
        return null;
    }
    // ShipmentDao 는 어떤 ConnectionMaker 구현인지 모름
    // → 구체 무관
}

2.6 자기 점검 답변

인터페이스에만 의존한다는 의미는?

:
1. 인터페이스 의존:

  • ConnectionMaker 만
  1. 구체 모름:

    • N/D 구현 모름
  2. 방향:

    • DAO → 인터페이스 ← 구현
  3. 효과:

    • 구체 변경 무관

3️⃣ 외부에서 주입 (생성자)

3.1 생성자 주입

// 생성자 주입
public class ShipmentDao {
    private final ConnectionMaker connectionMaker;
    
    public ShipmentDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;   // 외부에서 받음
    }
}

3.2 외부 결정

외부 결정:

  구현체 선택:
    - ShipmentDao 가 X
    - 외부 (조립하는 곳)

  → 생성 시 주입

3.3 조립

// 외부에서 조립
ConnectionMaker connectionMaker = new NConnectionMaker();   // 구현 선택
ShipmentDao dao = new ShipmentDao(connectionMaker);          // 주입

// D사로 바꾸려면 (ShipmentDao 변경 X)
ConnectionMaker dMaker = new DConnectionMaker();
ShipmentDao dDao = new ShipmentDao(dMaker);

3.4 주입의 의미

주입의 의미:

  의존성 (ConnectionMaker):
    - 외부에서 넣어줌 (inject)
    - DAO 가 생성 X

  → DI (의존성 주입)
  → Phase 8

3.5 ILIC 의 맥락

// 외부 조립 (main 또는 팩토리)
public class ShipmentApp {
    public static void main(String[] args) throws Exception {
        // 외부에서 구현체 결정 + 주입
        ConnectionMaker connectionMaker = new CustomerAConnectionMaker();
        ShipmentDao dao = new ShipmentDao(connectionMaker);   // 주입
        
        dao.add(new Shipment());
        
        // 다른 고객사 (ShipmentDao 코드 변경 X)
        ConnectionMaker bMaker = new CustomerBConnectionMaker();
        ShipmentDao bDao = new ShipmentDao(bMaker);   // 다른 구현 주입
        // ShipmentDao 는 그대로, 주입만 다름
    }
}
// 구현체 선택/조립은 외부 (main)
// → 나중에 IoC 컨테이너가 (Phase 8)

3.6 자기 점검 답변

외부에서 주입받기 (생성자) 의 방식은?

:
1. 생성자 주입:

  • 생성자로 받음
  1. 외부 결정:

    • 조립하는 곳
  2. 조립:

    • new 구현 → 주입
  3. 의미:

    • DI (의존성 주입)

4️⃣ 새 DB에도 변경 안 됨

4.1 ShipmentDao 불변

새 DB → ShipmentDao 불변:

  새 DB 지원:
    - 새 ConnectionMaker 구현
    - ShipmentDao 코드 변경 X

  → 확장에 열림, 변경에 닫힘 (OCP)

4.2 새 구현만 추가

// 새 DB = 새 구현만
public class PostgreSqlConnectionMaker implements ConnectionMaker {
    public Connection makeConnection() throws Exception {
        // PostgreSQL 연결
        return null;
    }
}

// ShipmentDao 는 전혀 안 건드림
ShipmentDao dao = new ShipmentDao(new PostgreSqlConnectionMaker());

4.3 변경 격리

변경 격리:

  DB 추가/변경:
    - ConnectionMaker 영역만
    - ShipmentDao 격리

  → 영향 차단

4.4 상속 대비 개선

상속 (Phase 4) 대비:

상속:
  - 새 DB = 새 서브클래스
  - 단일 상속 제약
  - 컴파일 타임

인터페이스 + 합성:
  - 새 DB = 새 구현
  - 제약 없음
  - 런타임 주입

4.5 ILIC 의 맥락

// 새 DB 추가 시나리오

// 기존
public interface ConnectionMaker {
    Connection makeConnection() throws Exception;
}
public class ShipmentDao {
    private final ConnectionMaker connectionMaker;
    public ShipmentDao(ConnectionMaker cm) { this.connectionMaker = cm; }
    public void add(Shipment s) throws Exception {
        Connection c = connectionMaker.makeConnection();
    }
}

// 새 DB (MariaDB) 추가
// → 새 구현만 작성 (ShipmentDao 안 건드림)
public class MariaDbConnectionMaker implements ConnectionMaker {
    public Connection makeConnection() throws Exception {
        return DriverManager.getConnection(
            "jdbc:mariadb://localhost/ilic", "root", "pass");
    }
}

// 사용 (주입만)
ShipmentDao dao = new ShipmentDao(new MariaDbConnectionMaker());
// ShipmentDao 코드 변경 0줄 (OCP)

4.6 자기 점검 답변

ShipmentDao가 새 DB 추가에도 변경 안 되는 이유는?

:
1. 불변:

  • 인터페이스 의존
  1. 새 구현만:

    • ConnectionMaker 추가
  2. 격리:

    • DAO 영향 X
  3. OCP:

    • 확장 열림, 변경 닫힘

5️⃣ 결합도란

5.1 결합도

결합도 (Coupling):

  모듈 간 의존 정도.
  - 높으면: 강하게 묶임
  - 낮으면: 느슨

→ 낮을수록 좋음

5.2 강결합

강결합:

  구체 클래스 직접 의존:
    - new NConnectionMaker()
    - 변경 시 영향

  → 묶임

5.3 느슨한 결합

느슨한 결합:

  인터페이스 의존:
    - ConnectionMaker
    - 구현 무관

  → 유연

5.4 결합도 낮추기

결합도 낮추기:

  1. 인터페이스 의존
  2. 주입 (외부 결정)
  3. 구체 분리

→ 변경 영향 ↓

5.5 응집도와 함께

응집도와 함께:

  좋은 설계:
    - 높은 응집도 (관련 모음)
    - 낮은 결합도 (모듈 느슨)

  인터페이스 + 합성:
    - 결합도 ↓

5.6 ILIC 의 맥락

// 강결합 (전)
public class ShipmentDaoTight {
    public void add(Shipment s) throws Exception {
        // 구체 직접 (강결합)
        Connection c = new NConnectionMaker().makeConnection();
        // NConnectionMaker 에 묶임
    }
}

// 느슨한 결합 (후)
public class ShipmentDaoLoose {
    private final ConnectionMaker connectionMaker;   // 인터페이스
    public ShipmentDaoLoose(ConnectionMaker cm) {
        this.connectionMaker = cm;   // 주입
    }
    public void add(Shipment s) throws Exception {
        Connection c = connectionMaker.makeConnection();   // 인터페이스
        // 어떤 구현인지 모름 (느슨)
    }
}
class NConnectionMaker implements ConnectionMaker {
    public Connection makeConnection() { return null; }
}

5.7 자기 점검 답변

결합도 (coupling) 란?

:
1. 결합도:

  • 모듈 간 의존
  • 낮을수록 좋음
  1. 강결합:

    • 구체 직접 의존
  2. 느슨:

    • 인터페이스 의존
  3. 낮추기:

    • 인터페이스 + 주입

6️⃣ 구현체 결정 책임

6.1 결정 책임 이동

결정 책임 이동:

  "어떤 ConnectionMaker?"

전 (DAO 가 결정):
  - new NConnectionMaker()
  - DAO 가 구체 선택

후 (외부가 결정):
  - 외부에서 주입
  - DAO 는 모름

6.2 누구에게

누구에게:

  구현체 결정:
    - ShipmentDao X
    - 외부 (조립자)
      - main
      - 팩토리
      - IoC 컨테이너 (나중)

6.3 관심사 분리

관심사 분리:

  ShipmentDao:
    - 데이터 접근 (사용)
    - 구현 선택 책임 X

  외부:
    - 구현 선택/조립 책임

  → 책임 분리

6.4 조립 책임

// 조립 책임 (외부)
public class DaoFactory {
    public ShipmentDao shipmentDao() {
        // 구현체 결정 책임 (외부)
        return new ShipmentDao(connectionMaker());
    }
    public ConnectionMaker connectionMaker() {
        return new NConnectionMaker();   // 여기서 결정
    }
}
// ShipmentDao 는 사용만, DaoFactory 가 조립

6.5 ILIC 의 맥락

// 구현체 결정 책임 = 외부 (DaoFactory)
public class DaoFactory {
    
    // 결정 + 조립 책임
    public ShipmentDao shipmentDao() {
        return new ShipmentDao(connectionMaker());   // 주입
    }
    
    public ConnectionMaker connectionMaker() {
        // 어떤 구현? 여기서 결정
        return new CustomerAConnectionMaker();
    }
    
    public BookingDao bookingDao() {
        return new BookingDao(connectionMaker());   // 같은 연결 재사용
    }
}

// ShipmentDao 는 결정 책임 없음 (사용만)
// DaoFactory 가 결정/조립
// → IoC 컨테이너로 발전 (Phase 8)

class BookingDao {
    BookingDao(ConnectionMaker cm) { }
}
class CustomerAConnectionMaker implements ConnectionMaker {
    public Connection makeConnection() { return null; }
}

6.6 자기 점검 답변

구현체 결정 책임은 누구에게인가?

:
1. 이동:

  • DAO → 외부
  1. 누구:

    • 조립자 (main/팩토리/컨테이너)
  2. 관심사 분리:

    • DAO: 사용
    • 외부: 조립
  3. 조립 책임:

    • DaoFactory

7️⃣ DIP와의 연결

7.1 DIP

DIP (Dependency Inversion Principle):

  "구체가 아닌 추상에 의존하라"
  - 고수준 ↛ 저수준
  - 둘 다 추상화에

7.2 의존 역전

의존 역전:

전 (직접):
  ShipmentDao → NConnectionMaker (구체)

후 (역전):
  ShipmentDao → ConnectionMaker (추상)
                     ↑
              NConnectionMaker (구체)

  → 구체가 추상에 의존 (역전)

7.3 고수준/저수준

고수준/저수준:

  고수준: ShipmentDao (정책)
  저수준: 연결 구현 (세부)

  DIP:
    - 고수준이 저수준 직접 의존 X
    - 둘 다 ConnectionMaker (추상)

7.4 인터페이스 소유

인터페이스 소유:

  ConnectionMaker:
    - 고수준 (ShipmentDao) 가 정의
    - 저수준이 구현

  → 고수준이 계약 정의
  → 진정한 역전

7.5 ILIC 의 맥락

// DIP 적용

// 추상 (고수준이 정의한 계약)
public interface ConnectionMaker {
    Connection makeConnection() throws Exception;
}

// 고수준 (정책) — 추상에 의존
public class ShipmentDao {
    private final ConnectionMaker connectionMaker;   // 추상 의존
    public ShipmentDao(ConnectionMaker cm) { this.connectionMaker = cm; }
}

// 저수준 (세부) — 추상 구현
public class NConnectionMaker implements ConnectionMaker {   // 추상 구현
    public Connection makeConnection() throws Exception {
        return null;
    }
}

// 의존 방향:
// ShipmentDao (고수준) → ConnectionMaker (추상) ← NConnectionMaker (저수준)
// 고수준이 저수준에 직접 의존 X
// → DIP 달성

7.6 자기 점검 답변

DIP (의존 역전 원칙) 와의 연결은?

:
1. DIP:

  • 추상에 의존
  1. 역전:

    • 구체가 추상에 의존
  2. 고/저수준:

    • 둘 다 추상화에
  3. 인터페이스 소유:

    • 고수준이 계약 정의

8️⃣ 상속에서 합성으로

8.1 전환

상속 → 합성:

Phase 4~5 (상속):
  - 추상클래스 상속
  - getConnection 오버라이드

Phase 6 (합성):
  - ConnectionMaker 보유
  - 주입

8.2 코드 비교

// 상속 (Phase 4)
abstract class ShipmentDao {
    abstract Connection getConnection();   // 상속으로 구현
}
class MySqlShipmentDao extends ShipmentDao {
    Connection getConnection() { return null; }
}

// 합성 (Phase 6)
class ShipmentDao {
    private final ConnectionMaker connectionMaker;   // 보유
    ShipmentDao(ConnectionMaker cm) { this.connectionMaker = cm; }
}
// 상속 X, 주입

8.3 합성의 이점 재확인

합성 이점:

  - 단일 상속 제약 X
  - 런타임 교체
  - 느슨한 결합
  - 테스트 (Mock)
  - 여러 협력 객체

8.4 전략 패턴 예고

전략 패턴 예고:

  ConnectionMaker (전략):
    - 변하는 알고리즘 (연결)

  ShipmentDao (컨텍스트):
    - 전략 사용

  → Unit 6.3 전략 패턴

8.5 ILIC 의 맥락

// 상속 → 합성 전환 완료

// 합성 기반 (Phase 6)
public class ShipmentDao {
    private final ConnectionMaker connectionMaker;   // 합성
    
    public ShipmentDao(ConnectionMaker connectionMaker) {
        this.connectionMaker = connectionMaker;   // 주입
    }
    
    public void add(Shipment shipment) throws Exception {
        Connection c = connectionMaker.makeConnection();   // 위임
        // ...
    }
}

// 효과 (상속 한계 모두 극복):
// 1. ShipmentDao 가 다른 클래스 상속 가능
// 2. 런타임 ConnectionMaker 교체
// 3. 느슨한 결합
// 4. Mock 으로 테스트
// 5. 여러 협력 객체 (Logger, Metrics 등) 추가 가능

// → 전략 패턴 (Unit 6.3)
interface ConnectionMaker { Connection makeConnection() throws Exception; }

8.6 자기 점검 답변

상속에서 합성으로의 전환은?

:
1. 전환:

  • 상속 → 합성
  1. 코드:

    • 오버라이드 → 보유/주입
  2. 이점:

    • 제약 X, 런타임, 테스트
  3. 전략 패턴:

    • ConnectionMaker (전략)

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
인터페이스 도입?ConnectionMaker 추상화
인터페이스 의존?구체 모름
외부 주입?생성자, 조립자 결정
새 DB 불변?새 구현만, DAO 그대로
결합도?모듈 간 의존 (낮을수록 좋음)
결정 책임?외부 (조립자)
DIP?추상에 의존 (역전)
합성 이점?제약 X, 런타임
상속 → 합성?보유 + 주입
전략 패턴?ConnectionMaker (전략)

9.2 자기 점검 체크리스트

인터페이스 도입

  • ConnectionMaker

인터페이스 의존

  • 구체 모름

외부 주입

  • 생성자

새 DB 불변

  • OCP

결합도

  • 낮추기

결정 책임

  • 외부

DIP

  • 역전

합성

  • 전환

9.3 추가 심화 질문

Q1: 인터페이스 vs 추상클래스 선택?

답:

  • 인터페이스: 계약, 다중 구현, 합성
  • 추상클래스: 공통 구현 + 상태
  • 변동 주입은 인터페이스
  • 둘 다 가능 (조합)

Q2: 생성자 주입 vs Setter 주입?

답:

  • 생성자: 불변(final), 필수 의존
  • Setter: 선택적, 변경 가능
  • 생성자 권장 (Phase 8)
  • 순환 참조 감지

Q3: 결합도 종류?

답:

  • 데이터 결합 (느슨)
  • 제어 결합
  • 공통 결합
  • 내용 결합 (강함)
  • 낮은 결합 지향

Q4: 인터페이스 분리 원칙 (ISP)?

답:

  • 큰 인터페이스 분리
  • 클라이언트가 안 쓰는 메서드 의존 X
  • 작고 응집된 인터페이스
  • SOLID 의 I

Q5: 의존성 주입 없이 인터페이스만?

답:

  • 인터페이스만으로 부족
  • 구현 선택 어디선가
  • 주입으로 외부화
  • 인터페이스 + 주입 = 완성

🎯 핵심 요약 — 3줄 정리

1. 인터페이스 도입

  • ConnectionMaker 인터페이스로 변하는 부분 추상화
  • ShipmentDao 는 인터페이스에만 의존 (구체 모름)

2. 외부 주입 + 결합도

  • 구현체를 외부에서 생성자로 주입 (조립자가 결정)
  • 새 DB = 새 구현만, ShipmentDao 불변 (결합도 ↓)

3. DIP와 합성

  • 고수준이 추상에 의존 (의존 역전)
  • 상속 → 합성 전환 → 전략 패턴 (다음)

📚 다음으로...

Unit 6.2 — OCP (개방폐쇄원칙) ★깊이

이번 Unit에서 인터페이스 결합도 낮추기를 봤다면, 다음은 OCP (★ 깊이 파기).

  • 확장 열림, 변경 닫힘
  • OCP 만족 확인
  • OCP 깨지는 신호 (if-else)
  • OCP 와 SRP 연결

Phase 6 진행 상황

🌱 Phase 6 — OCP & 전략 패턴
  ✅ Unit 6.1 인터페이스로 결합도 낮추기 ← 여기
  ⏭ Unit 6.2 OCP (개방폐쇄원칙) ★깊이
  ⏭ Unit 6.3 전략 패턴

5주차 누적 진행

✅ Part A — 동시성 마무리 (7 Unit)
🌱 Part B — 토비의 스프링
  ✅ Phase 3~5 (9 Unit)
  🌱 Phase 6 — OCP & 전략 패턴 (1/3 진행)

총: 17/26 Unit

🌱 Phase 6 시작 — 인터페이스 + 합성

profile
Software Developer

0개의 댓글