조영호 님의 "오브젝트" 책을 정리한 글입니다.
1. 데이터 중심의 영화 예매 시스템
데이터를 준비하자
데이터 중심의 설계
- 객체 내부에 저장되는 데이터를 기반으로 시스템을 분할하는 방법
- 객체 내부에 저장해야 하는 "데이터가 무엇인가"를 묻는 것으로 시작됨
예제
더보기
public class Movie {
private String title;
private Duration runningTime;
private Money fee;
private List<DiscountCondition> discountConditions;
private MovieType movieType;
private Money discountAmount;
private double discountPercent;
public getFee() { return fee; }
public setFee(Money fee) { this.fee = fee; }
// getter, setter
}
public class DiscountCondition {
private DiscountConditionType type;
private int sequence;
private DayOfWeek dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;
}
항목 | Movie | DiscountCondition |
역할 | 영화 정보 및 할인 조건 포함 | 할인 조건을 정의하는 클래스 |
구성 요소 | discountConditions 리스트 포함 | type 변수(할인 조건의 종류) 포함 |
할인 정책 관리 | 할인 금액과 할인 비율을 직접 정의 | 할인 조건을 저장하고 관리 |
영화를 예매하자
public class ReservationAgency {
public Reservation reserve(Screening screening, Customer customer, int audienceCount) {
Movie movie = screening.getMovie();
boolean discountable = false;
for (DiscountCondition condition : movie.getDiscountConditions()) {
if (condition.getType() == DiscountConditionType.PERIOD) {
discountable = screening.getWhenScreened().getDayOfWeek().equals(condition.getDayOfWeek()) &&
condition.getStartTime().compareTo(screening.getWhenScreened().toLocalTime()) <= 0 &&
condition.getEndTime().compareTo(screening.getWhenScreened().toLocalTime()) >= 0;
} else {
discountable = condition.getSequence() == screening.getSequence();
}
if (discountable) break;
}
Money fee;
if (discountable) {
Money discountAmount = Money.ZERO;
switch (movie.getMovieType()) {
case AMOUNT_DISCOUNT:
discountAmount = movie.getDiscountAmount();
break;
case PERCENT_DISCOUNT:
discountAmount = movie.getFee().times(movie.getDiscountPercent());
break;
case NONE_DISCOUNT:
discountAmount = Money.ZERO;
break;
}
fee = movie.getFee().minus(discountAmount);
} else {
fee = movie.getFee();
}
return new Reservation(customer, screening, fee, audienceCount);
}
}
- 할인 가능 여부 확인
- 적절한 할인 정책에 따라 예매 요금 계산
2. 설계 트레이드오프
캡슐화
개념 | 설명 |
정의 |
외부에서 알 필요 없는 부분을 감추고, 필요한 부분만 공개하는 것
|
목적 |
- 내부 구현을 숨기고 외부에서 접근할 수 없도록 보호
- 외부에서는 인터페이스만 사용하여 내부 구현 변경 영향을 최소화 |
구성 요소 |
- 상태(State): 객체가 관리하는 데이터 (필드, 변수)
- 행동(Behavior): 객체가 수행하는 기능 (메서드) |
이점 |
- 내부 구현 변경 가능 (외부 영향 최소화)
- 코드 유지보수 용이 - 인터페이스 안정성 확보 (외부와의 의존성 감소) |
설계 원칙 |
- 내부 구현은 자주 변경됨 → 숨기는 것이 좋음
- 외부 인터페이스는 변경 가능성이 적음 → 신중하게 설계 |
응집도와 결합도
개념 | 설명 |
응집도 (Cohesion) |
모듈 내부 요소들이 얼마나 밀접하게 연관되어 있는지
|
결합도 (Coupling) | 한 모듈이 다른 모듈에 얼마나 의존하는지 (다른 모듈에 대해 얼마나 많은 지식을 가지고 있는가) |
좋은 설계 원칙
- 변경이 용이하고 유지보수성이 뛰어남
- 높은 응집도: 오직 하나의 모듈만 수정하면 됨 (코드 변경 용이)
- 낮은 결합도: 독립적으로 변경이 가능함 (영향 범위 최소화)
캡슐화와의 관계
- 캡슐화를 잘 지키면
- 응집도가 높아지고: 모듈 내부의 관련 기능이 강하게 결합됨
- 결합도가 낮아짐: 모듈 간 의존성이 줄어 변경에 강해짐
3. 데이터 중심의 영화 예매 시스템의 문제점
캡슐화 위반
- Movie의 getter, setter 메서드를 외부에 노출
- 내부 데이터(Movie)의 존재 여부를 드러냄
- 원인은 책임이 아닌 데이터에 초점을 맞추었기 때문
- 캡슐화할 수 있는 적절한 책임은 협력이라는 문맥을 고려할 때만 얻을 수 있음
높은 결합도
개념 | 설명 |
정의 |
한 객체가 다른 객체의 내부 구현에 의존하여 강하게 연결된 상태
|
특징 |
- 내부 구현이 노출됨 → 클라이언트가 내부 구현을 직접 참조
- 변경 영향이 큼 → 하나의 변경이 여러 곳에 영향을 미침 |
예시 |
- ReservationAgency.reserver()가 Movie.getFee()를 직접 호출하여 의존함
- ReservationAgency가 DiscountCondition 등 여러 데이터 객체에 직접 의존 |
문제점 |
🚨 변경이 어려움 → 특정 객체를 변경하면 관련된 모든 객체도 변경해야 함
🚨 유지보수 비용 증가 → 수정할 때 영향 범위를 파악해야 함 |
해결 방법 |
✅ 캡슐화 강화 → 내부 구현을 숨기고 인터페이스만 공개
|
낮은 응집도
개념 | 설명 |
정의 |
모듈 내부의 요소들이 서로 관련성이 낮은 상태
|
특징 |
- 코드 수정 이유가 다양함 → 여러 책임이 한 모듈에 섞여 있음
- 불필요한 영향 발생 → 변경이 필요 없는 코드도 영향을 받음 |
예시 |
- ReservationAgency가 할인 정책과 할인 조건을 동시에 관리
→ 새로운 할인 정책 추가 시, 할인 조건 코드에 영향을 미칠 수 있음 - MovieType 열거형에 새로운 할인 정책 추가 시 → 관련된 분기문을 추가해야 함 |
문제점 |
🚨 확장성 저하 → 새로운 기능 추가 시, 여러 모듈을 수정해야 함
🚨 유지보수 어려움 → 하나의 변경이 연관 없는 코드까지 영향을 미침 |
해결 방법 |
✅ 단일 책임 원칙 (SRP) 준수 → 각 모듈이 하나의 역할만 수행하도록 분리
✅ 책임 기반 분리 → 할인 정책과 할인 조건을 별도 클래스로 분리 |
4. 자율적인 객체를 향해
캡슐화를 지켜라
- 낮은 응집도와 높은 결합도를 막기 위해서는 캡슐화를 지켜야 함
스스로 자신의 데이터를 책임지는 객체
- 객체라는 단위에 상태와 행동을 하나로 묶는것은 자신의 상태를 스스로 처리할 수 있게 하기 위함
- 내부 데이터보다 객체의 책임을 정의하는 것이 더 중요
- 객체를 설계할 때 이 객체가 어떤 데이터를 포함해야 하는 가라는 질문은 두가지 질문으로 분리해야 함
- 이 객체가 어떤 데이터를 포함해야 하는가?
- 이 객체가 데이터에 대해 수행해야 하는 행동은 무엇인가?
"이 객체가 데이터에 대해 수행해야 하는 행동은 무엇인가?" 라는 질문에 대해 메서드를 추가
더보기
public class DiscountCondition {
private DiscountConditionType type;
private int sequence;
private DayOfWeek dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;
// getter..
public boolean isDiscountable(DayOfWeek dayOfWeek, LocalTime time) {
if (type != DiscountConditionType.PERIOD) throw new IllegalArgumentException();
return this.dayOfWeek.equals(dayOfWeek) &&
this.startTime.compareTo(time) <= 0 &&
this.endTime.compareTo(time) >= 0;
}
public boolean isDiscountable(int sequence) {
if (type != DiscountConditionType.SEQUENCE) throw new IllegalArgumentException();
return this.sequence == sequence;
}
}
public class Movie {
private String title;
private Duration runningTime;
private Money fee;
private List<DiscountCondition> discountConditions;
private MovieType movieType;
private Money discountAmount;
private double discountPercent;
// getter...
public Money calculateAmountDiscountedFee() {
if (movieType != MovieType.AMOUNT_DISCOUNT) throw new IllegalArgumentException();
return fee.minus(discountAmount);
}
public Money calculatePercentDiscountedFee() {
if (movieType != MovieType.PERCENT_DISCOUNT) throw new IllegalArgumentException();
return fee.minus(fee.times(discountPercent));
}
public Money calculateNoneDiscountedFee() {
if (movieType != MovieType.NONE_DISCOUNT) throw new IllegalArgumentException();
return fee;
}
}
- Screening, ReservationAgency의 결합도 감소
5. 하지만 여전히 부족하다
캡슐화 위반
예시 | 문제점 |
DiscountCondition.isDiscountable(DayOfWeek, LocalDate) | 객체 내부에 해당 인스턴수 변수가 포함되어 있는것을 알 수 있음 내부 상태가 변경되면 메서드도 수정되어 클라이언트도 함께 수정해야 함 |
Movie.calculateXXXDiscountFee() | 할인 정책이 세가지 존재하는 것을 드러냄 (금액, 비율, 미적용) 새로운 정책이 추가되거나 제거된다면 클라이언트도 함께 수정해야 함 |
높은 결합도
낮은 응집도
6. 데이터 중심 설계의 문제점
데이터 중심 설계는 객체의 행동보다는 상태에 초점을 맞춘다
- 데이터 주도 설계는 너무 이른 시기에 내부 구현에 초점을 맞추게 함 (설계 시작부터 데이터에 관해 결정하도록 강요)
- 이러한 순서의 캡슐화는 데이터에 대한 지식이 인터페이스에 고스란히 드러나게 함
데이터 중심 설계는 객체를 고립시킨 채 오퍼레이션을 정의하도록 만든다
- 협력이라는 문맥안에서 필요한 책임을 결정하고 이를 수행할 적절한 객체를 결정하는 것이 중요
- 객체지향 설계의 무게중심은 항상 객체의 내부가 아니라 외부에 맞춰져 있어야 함
- 중요한 것은 다른 객체와 협력하는 방법
'Code > OOP' 카테고리의 다른 글
[오브젝트] 6. 메시지와 인터페이스 (0) | 2025.04.04 |
---|---|
[오브젝트] 5. 책임 할당하기 (0) | 2025.04.03 |
[오브젝트] 3. 역할, 책임, 협력 (0) | 2025.04.03 |
[오브젝트] 2. 객체지향 프로그래밍 (1) | 2025.04.02 |
[객체지향의 사실과 오해] 2. 이상한 나라의 객체 (1) | 2025.04.02 |