조영호 님의 "오브젝트" 책을 정리한 글입니다.
1. 의존성 이해하기
변경과 의존성
의존성
- 함께 협력하는 객체들 사이에 발생하는 관계
구분 | 설명 |
구현 시점 | - 의존 대상 객체가 변경되면, 의존하는 객체도 함께 변경될 가능성이 있음 |
실행 시점 | - 의존 객체가 정상 동작하려면, 의존 대상 객체가 반드시 존재해야 함 |
방향성 |
- 항상 단방향임 (의존 A → B: A는 B에 의존하지만, B는 A에 의존하지 않음)
|
변경 전파 | - 의존 방향을 따라 변경이 전파됨 (즉, B가 바뀌면 A도 영향 받을 수 있음) |
예시
더보기
public class PeriodCondition implements DiscountCondition {
private DayOfWeek dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;
// constructor...
public boolean isSatisfiedBy(Screening screening) {
return this.dayOfWeek.equals(screening.getWhenScreened().getDayOfWeek()) &&
this.startTime.compareTo(screening.getWhenScreened().toLocalTime()) <= 0 &&
this.endTime.compareTo(screening.getWhenScreened().toLocalTime()) >= 0;
}
}
- PeriodCondition 인스턴스가 정상적으로 동작하기 위해서는 Screening 인스턴스가 존재해야 함
UML 표기법
관계 | 기호 | 의미 | 예시 |
인터페이스 구현 | -----▷ | implements 관계 | class Dog implements Animal |
상속 | ──▷ | extends 관계 | class Dog extends Mammal |
의존 (Dependency) | -----▶ | 메서드 인자 등 잠깐 사용 | class Dog가 Toy를 메서드 인자로 받음 |
연관 (Association) | ──▶ | 멤버 변수로 참조 | class Person has Address 필드 |
- 집합 (Aggregation) | ◇── | has-a (느슨한 포함) | 학급 ◇── 학생 |
- 구성 (Composition) | ◆── | has-a (강한 포함, 생명주기 공유) | 자동차 ◆── 엔진 |
의존성 전이
- 의존하고 있는 대상이 변경되면, 그 대상에 의존하고 있는 다른 클라이언트 코드들도 연쇄적으로 영향을 받는 현상
- 얼마나 영향이 전파될지는 변경의 방향과 캡슐화의 정도에 따라 달라짐
구분 | 정의 | 영향 |
직접 의존성 | 어떤 요소가 다른 요소를 직접 참조하거나 사용하는 경우 | PaymentService가 변경되면 OrderService에 직접 영향 |
간접 의존성 | A가 직접 의존하지 않지만, 중간 객체를 통해 간접적으로 의존하게 되는 경우 | PaymentService가 변경되면 OrderController까지 전이적 영향 가능 |
런타임 의존성과 컴파일타임 의존성
구분 | 컴파일타임 의존성 | 런타임 의존성 |
정의 | 코드 작성 시점에 명시적으로 드러나는 의존성 | 프로그램 실행 시 실제로 연결되는 의존성 |
확정 시점 | 컴파일 시 | 실행 시 |
유연성 | 낮음 (의존 대상이 고정됨) | 높음 (동적으로 결정 가능) |
확장성/유지보수 | 낮음 (변경 시 코드 수정 많음) | 높음 (구현 변경이 쉬움) |
결합도 | 상대적으로 높음 | 상대적으로 낮음 |
특징 | 타입은 명확하지만, 변경에 취약 | 느슨한 결합, 테스트 용이 |
컨텍스트 독립성
항목 | 설명 |
정의 |
모듈이 특정 실행 환경에 강하게 결합되지 않고, 다양한 환경에서 재사용 가능하도록 설계되는 것
|
문제점 (컨텍스트 의존) |
특정 컨텍스트에 강하게 의존하면, 다른 문맥에서 재사용/테스트하기 어려움
|
좋은 설계 방향 |
클래스가 최소한의 가정만으로 동작하도록 설계
(자신이 실행될 컨텍스트에 대한 구체적인 정보를 최대한 적게 알아야 함) |
장점 |
- 테스트 용이
- 재사용성 향상 - 유연한 구조 - 유지보수성 증가 |
의존성 해결하기
- 컴파일 타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하기
해결 방법
구분 | 생성자 주입 | Setter 주입 | 메서드 인자 주입 |
타이밍 | 객체 생성 시 주입 | 객체 생성 후 설정 | 메서드 호출 시점 |
안정성 | ✅ 객체가 항상 유효한 상태로 생성 | ❌ 설정 전까지는 불안정할 수 있음 | ✅ 전체 상태와 무관 |
불변성 보장 | ✅ 주입된 의존성 변경 불가 | ❌ 주입된 의존성을 나중에 변경 가능 | ✅ 일회성 사용 (상태 유지 X) |
유연성 | 구조적으로 안정적인 설계에 사용 | 동적으로 설정 또는 변경이 필요한 경우 | 일시적인 의존성에 적합 |
사용 예시 | 필수 의존성이 필요한 서비스나 모듈 | 선택적 기능/외부 설정 등이 있는 경우 | 유틸성 메서드 |
2. 유연한 설계
의존성과 결합도
바람직한 의존성
- 추상 계층에 의존하도록 하기
- 협력할 객체의 클래스를 고정할 필요 없음
- 장점
- 재사용성: 컨텍스트에 독립적
결합도
- 의존성의 바람직한 정도
지식이 결합을 낳는다
결합도의 정도
- 한 요소가 자신이 의존하고 있는 다른 요소에 대해 알고 있는 정보의 양으로 결정됨
- 결합도를 낮추기 위해서는 의존성의 내부 정보에 대해 적게 알고 있어야 하며 의존성은 자신의 정보를 최대한 숨겨야 함
추상화에 의존하라
- 어떤 구조를 좀더 명확하게 알기 위해 자세한 내용을 감춤으로써 복잡도를 극복하는 방법
- 대상에 대해 알아야 할 지식의 양을 줄일 수 있음
항목 | 추상 클래스 | 인터페이스 |
역할 | 부분 구현을 제공하는 추상적 틀 | 전적으로 동작 계약(Contract)을 정의 |
내부 구현 은닉 | ✅ 가능 (구현된 메서드로 내부 동작 일부 숨김) |
❌ 불가능 (구현 없음 → 동작 자체를 정의하지 않음)
|
자식 클래스 은닉 | ❌ 자식 클래스 종류는 외부에서 알아야 할 수 있음 | ✅ 자식 클래스(구현체)를 몰라도 사용 가능 |
상속 계층 필요성 | 🔍 필요 (상속 구조를 알아야 전체 구조 파악 가능) | ❌ 불필요 (계약만 알면 사용 가능) |
추상화 수준 | 중간 수준의 추상화 | 가장 높은 수준의 추상화 |
명시적인 의존성
항목 | 명시적인 의존성 | 숨겨진 의존성 |
정의 | 의존성이 외부에 드러나 있음 (생성자, setter, 메서드 인자 등) |
내부에서 직접 생성 (new 등으로 직접 인스턴스화)
|
인터페이스 노출 | ✅ 퍼블릭 인터페이스에 드러남 |
❌ 퍼블릭 인터페이스에 드러나지 않음
|
의존성 파악 용이성 | ✅ 외부에서 쉽게 파악 가능 |
❌ 코드 내부를 들여다봐야 알 수 있음
|
재사용성 | ✅ 다른 컨텍스트에서도 쉽게 재사용 가능 |
❌ 재사용하려면 내부 구현 수정 필요
|
유연성 | ✅ 의존 객체 교체 용이 (DI 등 가능) |
❌ 의존 객체 고정됨 (유연성 낮음)
|
예시 | Service(UserRepository repo) ← 외부에서 주입 |
new UserRepository() ← 생성자 내부에서 직접 생성
|
new는 해롭다
단점
단점 | 설명 |
🔗 결합도 높음 | 구체 클래스에 직접 의존함 |
📦 클래스 내부 책임 과다 | 생성과 사용의 책임을 한 클래스가 모두 가짐 |
🤯 사용자가 모든 걸 알아야 함 | 어떤 클래스인지, 어떤 인자를 넣어야 하는지, 생성 순서까지 모든 걸 클라이언트 코드가 알아야 함. |
해결 방법
전략 | 설명 |
🛠 생성과 사용의 분리 |
new는 클라이언트 코드가 하도록 위임하고, 클래스는 그저 받아서 사용만 함.
|
🧪 생성자 주입 (Constructor Injection) | 협력 객체를 생성자가 받음. 초기화도 깔끔하고, 필수 의존성 보장됨. |
🧴 Setter 주입 | 선택적인 의존성 주입. 늦게 설정할 수 있음, 하지만 누락 위험 존재. |
🧾 메서드 인자 주입 | 그때그때 일회용 의존성 주입. 긴밀한 관계가 아닌 경우 유용. |
가끔은 생성해도 무방하다
- 클래스 안에서 객체의 인스턴스를 직접 생성하는 방식이 유용한 경우도 있음
- ex) 협력하는 기본 객체를 설정하고 싶을 경우
표준 클래스에 대한 의존은 해롭지 않다
private List<DiscountCondition> conditions = new ArrayList<>();
- 변경될 확률이 거의 없는 표준 클래스는 의존해도 문제가 되지 않음
컨텍스트 확장하기
조합 가능한 행동
선언적으로 행동을 정의할 수 있음
- 객체들의 조합을 통해 무엇을 하는지를 표현할 수 있음
- 코드를 보는 것 만으로 어떤 일을 하는지 쉽게 파악할 수 있음
'Code > OOP' 카테고리의 다른 글
[오브젝트] 9. 유연한 설계 (0) | 2025.04.05 |
---|---|
[오브젝트] 6. 메시지와 인터페이스 (0) | 2025.04.04 |
[오브젝트] 5. 책임 할당하기 (0) | 2025.04.03 |
[오브젝트] 4. 설계 품질과 트레이드오프 (0) | 2025.04.03 |
[오브젝트] 3. 역할, 책임, 협력 (0) | 2025.04.03 |