아래는 조영호 저자의『오브젝트』 1장 “객체, 설계” 내용을 정리한 글입니다.
진정한 객체지향 패러다임으로의 전환은 클래스가 아니라 객체에 초점을 맞출 때에만 얻을 수 있다.
- 어떤 클래스를 만들지 고민하기 전에, 어떤 객체들이 필요한지 먼저 고민하라.
- 객체를 독립된 존재가 아니라, 기능을 구현하기 위해 협력하는 공동체의 일원으로 보아야 한다.
1. 도메인이란?
- 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야
2. 예제 UML
public class Screening {
private Movie movie;
private int sequence;
private LocalDateTime whenScreened;
public Screening(Movie movie, int sequence, LocalDateTime whenScreened) {
this.movie = movie;
this.sequence = sequence;
this.whenScreened = whenScreened;
}
public LocalDateTime getStartTime() {
return whenScreened;
}
public boolean isSequence(int sequence) {
return this.sequence == sequence;
}
public Money getMovieFee() {
return movie.getFee();
}
public Reservation reserve(Customer customer, int audienceCount) {
return new Reservation(
customer,
this,
calculateFee(audienceCount),
audienceCount
);
}
private Money calculateFee(int audienceCount) {
return movie.calculateMovieFee(this)
.times(audienceCount);
}
}
변수는 private, 메서드는 public
=> 경계의 명확성이 객체의 자율성을 보장하고, 프로그래머에게 구현의 자유를 제공한다.
3. 자율적인 객체
- 객체는 상태(state)와 행동(behavior)을 함께 가지는 복합적인 존재
- 객체는 스스로 판단하고 행동하는 자율적인 존재
- 캡슐화를 위해 접근 제어자를 적극 활용하자.
4. 프로그래머의 자유
프로그래머 역할을 두 가지로 구분하자.
- 클래스 작성자: 새로운 데이터 타입을 프로그램에 추가
- 클라이언트 프로그래머: 클래스 작성자가 추가한 데이터 타입을 사용
접근 제어자로 객체의 외부와 내부를 구분하면,
클라이언트 프로그래머가 알아야 할 지식의 양이 줄어들고
클래스 작성자는 구현을 자유롭게 변경할 수 있다.
5. 협력하는 객체들의 공동체
(1) Money 클래스
public class Money {
public static final Money ZERO = Money.wons(0);
private final BigDecimal amount;
public static Money wons(long amount) {
return new Money(BigDecimal.valueOf(amount));
}
public static Money wons(double amount) {
return new Money(BigDecimal.valueOf(amount));
}
private Money(BigDecimal amount) {
this.amount = amount;
}
public Money plus(Money other) {
return new Money(this.amount.add(other.amount));
}
public Money minus(Money other) {
return new Money(this.amount.subtract(other.amount));
}
public Money times(double percent) {
return new Money(
this.amount.multiply(BigDecimal.valueOf(percent))
);
}
public boolean isLessThan(Money other) {
return amount.compareTo(other.amount) < 0;
}
public boolean isGreaterThanOrEqual(Money other) {
return amount.compareTo(other.amount) >= 0;
}
}
(2) Reservation 클래스
public class Reservation {
private Customer customer;
private Screening screening;
private Money fee;
private int audienceCount;
public Reservation(
Customer customer,
Screening screening,
Money fee,
int audienceCount
) {
this.customer = customer;
this.screening = screening;
this.fee = fee;
this.audienceCount = audienceCount;
}
}
중요 포인트:
Screening,Movie,Reservation인스턴스들이 서로의 메서드를 호출하며 상호작용
6. 협력에 관한 짧은 이야기
- 객체는 다른 객체의 인터페이스에 공개된 행동을 요청(request) 할 수 있다.
- 요청을 받은 객체는 자율적으로 처리한 후 응답(response) 한다.
- 객체 간 상호작용의 유일한 방법은 메시지 전송(send a message) 이다.
- 메시지를 받으면 수신(receive a message) 했다고 표현하며,
이 메시지를 처리하기 위한 방법이 바로 메서드이다.
7. 할인 요금 계산을 위한 협력 시작하기
public class Movie {
private String title;
private Duration runningTime;
private Money fee;
private DiscountPolicy discountPolicy;
public Movie(
String title,
Duration runningTime,
Money fee,
DiscountPolicy discountPolicy
) {
this.title = title;
this.runningTime = runningTime;
this.fee = fee;
this.discountPolicy = discountPolicy;
}
public Money getFee() {
return fee;
}
public Money calculateMovieFee(Screening screening) {
return fee.minus(
discountPolicy.calculateDiscountAmount(screening)
);
}
}
중요 포인트 !
Movie클래스에는 할인 정책 자체가 구현되어 있지 않다.- 실제 할인 계산은
DiscountPolicy가 담당한다.
8. 할인 정책과 조건
- 할인 정책은 금액 할인(AmountDiscountPolicy) 과 비율 할인(PercentDiscountPolicy) 두 가지
- 부모 클래스
DiscountPolicy에 중복 코드를 두고,
자식 클래스가 실제 할인 금액을 제공하도록 설계
public abstract class DiscountPolicy {
private List<DiscountCondition> conditions;
public DiscountPolicy(DiscountCondition... conditions) {
this.conditions = Arrays.asList(conditions);
}
public Money calculateDiscountAmount(Screening screening) {
for (DiscountCondition c : conditions) {
if (c.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return Money.ZERO;
}
protected abstract Money getDiscountAmount(Screening screening);
}
(1) DiscountCondition 인터페이스
public interface DiscountCondition {
boolean isSatisfiedBy(Screening screening);
}
(2) SequenceCondition
public class SequenceCondition implements DiscountCondition {
private int sequence;
public SequenceCondition(int sequence) {
this.sequence = sequence;
}
@Override
public boolean isSatisfiedBy(Screening screening) {
return screening.isSequence(sequence);
}
}
(3) PeriodCondition
public class PeriodCondition implements DiscountCondition {
private DayOfWeek dayOfWeek;
private LocalTime startTime;
private LocalTime endTime;
public PeriodCondition(
DayOfWeek dayOfWeek,
LocalTime startTime,
LocalTime endTime
) {
this.dayOfWeek = dayOfWeek;
this.startTime = startTime;
this.endTime = endTime;
}
@Override
public boolean isSatisfiedBy(Screening screening) {
LocalDateTime when = screening.getStartTime();
return when.getDayOfWeek().equals(dayOfWeek)
&& startTime.compareTo(when.toLocalTime()) <= 0
&& endTime.compareTo(when.toLocalTime()) >= 0;
}
}
(4) AmountDiscountPolicy
public class AmountDiscountPolicy extends DiscountPolicy {
private Money discountAmount;
public AmountDiscountPolicy(
Money discountAmount,
DiscountCondition... conditions
) {
super(conditions);
this.discountAmount = discountAmount;
}
@Override
protected Money getDiscountAmount(Screening screening) {
return discountAmount;
}
}
(5) PercentDiscountPolicy
public class PercentDiscountPolicy extends DiscountPolicy {
private double percent;
public PercentDiscountPolicy(
double percent,
DiscountCondition... conditions
) {
super(conditions);
this.percent = percent;
}
@Override
protected Money getDiscountAmount(Screening screening) {
return screening.getMovieFee().times(percent);
}
}
9. 할인 정책 구성하기
- 하나의 영화에는 하나의 할인 정책만 설정 가능
- 여러 개의 할인 조건을 적용할 수 있다
Movie avatar = new Movie(
"아바타",
Duration.ofMinutes(120),
Money.wons(10000),
new AmountDiscountPolicy(
Money.wons(800),
new SequenceCondition(1),
new PeriodCondition(DayOfWeek.MONDAY, LocalTime.of(10, 0), LocalTime.of(11, 59)),
new PeriodCondition(DayOfWeek.THURSDAY, LocalTime.of(10, 0), LocalTime.of(20, 59))
)
);
10. 컴파일 시간 의존성과 실행 시간 의존성
- 컴파일 시점에
Movie는DiscountPolicy인터페이스만 알고 있다. - 실행 시점에 구체 구현(
AmountDiscountPolicy등)이 주입된다.
11. 템플릿 메서드 패턴
DiscountPolicy가 비즈니스 로직 흐름을 제공하고,- 자식 클래스에 구체 처리를 위임
- 이 패턴을 Template Method라고 부른다.
12. 프로그래밍 by Difference
- 부모 클래스와 다른 부분만 추가해 새로운 클래스를 쉽고 빠르게 만드는 방법
13. 상속과 다형성
- 자식 클래스는 부모 클래스가 수신할 수 있는 모든 메시지를 수신 가능
- 외부 객체는 자식 클래스를 부모 타입으로 간주할 수 있다
- 이를 업캐스팅(upcasting)이라고 한다
14. 인터페이스와 다형성
- 순수한 인터페이스만으로도 다형성을 구현할 수 있다
- 예:
DiscountPolicy인터페이스를 직접 구현하는NoneDiscountPolicy
15.추상화의 힘
DiscountPolicy는AmountDiscountPolicy,PercentDiscountPolicy보다 추상적- 추상화를 통해 요구사항의 정책을 높은 수준에서 표현
- 디자인 패턴과 프레임워크도 이 개념을 사용
16. 유연한 설계
public class NoneDiscountPolicy extends DiscountPolicy {
@Override
protected Money getDiscountAmount(Screening screening) {
return Money.ZERO;
}
}
Movie starWars = new Movie(
"스타워즈",
Duration.ofMinutes(210),
Money.wons(10000),
new NoneDiscountPolicy()
);
- 예외 케이스(할인 없음)를 0원 정책으로 처리하여 일관성 유지
17. 추상 클래스 vs 인터페이스 트레이드오프
public interface DiscountPolicy {
Money calculateDiscountAmount(Screening screening);
}
public abstract class DefaultDiscountPolicy implements DiscountPolicy {
private List<DiscountCondition> conditions;
public DefaultDiscountPolicy(DiscountCondition... conditions) {
this.conditions = Arrays.asList(conditions);
}
@Override
public Money calculateDiscountAmount(Screening screening) {
for (DiscountCondition each : conditions) {
if (each.isSatisfiedBy(screening)) {
return getDiscountAmount(screening);
}
}
return Money.ZERO;
}
protected abstract Money getDiscountAmount(Screening screening);
}
public class NoneDiscountPolicy implements DiscountPolicy {
@Override
public Money calculateDiscountAmount(Screening screening) {
return Money.ZERO;
}
}
18. 코드 재사용: 상속 vs 합성
- 상속은 캡슐화를 위반하고 설계를 경직되게 만든다.
- 합성(Composition)을 통해 다른 객체의 인스턴스를 포함하여 재사용하라.
- 예:
Movie가DiscountPolicy인스턴스를 참조하여 할인 로직 재사용
public class Movie {
private DiscountPolicy discountPolicy;
public void changeDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
중요!
유연성이 필요한 곳에 추상화를 사용하라.
(할인 정책, 할인 조건 등 다양한 종류가 존재하기 때문)
'Java > 오브젝트 (교재)' 카테고리의 다른 글
| 3장. 역할, 책임, 협력 (1) | 2025.05.12 |
|---|---|
| 1장. 객체, 설계 (0) | 2025.05.11 |