콘텐츠로 이동

오브젝트 실전 강의 교재

14장 — 일관성 있는 협력

원서: 조영호 『오브젝트』 대상: Java/Spring 백엔드 입문~중급 수강생 형식: 개념 → 비유 → 예시 → 핵심 교훈 → 현업 예제 → 함정 → 체크리스트 → 퀴즈(정답 분리)


0. 이 장을 시작하기 전에

0.1 학습 목표

  • 같은 객체 협력 패턴이 반복 등장 함을 알아본다.
  • 일관된 협력 패턴 을 설계로 정형화.
  • 핸드폰 과금 시스템의 4 가지 방식 (고정·시간대·요일·구간) 을 같은 협력 패턴으로 통합.
  • 15장 (디자인 패턴) 의 전제.

0.2 큰 그림 — 패턴의 발견

[ 첫 구현 ]                       [ 반복 발견 ]                    [ 정형화 ]
 고정요금 정책                      시간대별·요일별·구간별 정책       같은 협력 패턴
 한 클래스                          비슷한 구조 4개                  추상 + 다형성
                                                                     → 15장 디자인 패턴

비유 — "여러 가게의 같은 메뉴 패턴"

카페·식당·빵집이 모두 "주문 받기 → 만들기 → 결제 → 서빙" 같은 협력 패턴. 처음엔 각자 짜지만 패턴이 드러나면 일관된 구조로 정형화.

0.3 현업에서 왜 중요한가

  • 같은 코드를 반복 짤 때 패턴 인식 → 추상화 후보.
  • 디자인 패턴이 어떻게 자연 발견되는지 — 도그마 X.

1. 핸드폰 과금 시스템 변경하기 — 4 가지 방식

1.1 기본 정책 확장

기존: 고정요금 (RegularPolicy). 새 요구사항: - 고정요금 — 단순 단가 × 시간. - 시간대별 — 시간대 (주간·야간) 마다 다른 단가. - 요일별 — 요일마다 다른 단가. - 구간별 — 총 통화 시간 구간마다 다른 단가.

1.2 고정요금 방식 구현

public class FixedFeePolicy implements RatePolicy {
    private final Money amount;
    public Money calculateFee(Phone phone) {
        return amount.times(phone.totalSeconds());
    }
}

1.3 시간대별 방식 구현

public class TimeOfDayDiscountPolicy implements RatePolicy {
    private final List<TimeRule> rules;   // 시간대별 규칙

    public Money calculateFee(Phone phone) {
        Money result = Money.ZERO;
        for (Call call : phone.calls()) {
            for (TimeRule rule : rules) {
                if (rule.applies(call.from(), call.to())) {
                    result = result.plus(rule.amount().times(call.seconds()));
                }
            }
        }
        return result;
    }
}

1.4 요일별 방식 / 구간별 방식

비슷한 구조 — 다른 기준 (요일·구간) 으로 분기.


2. 설계에 일관성 부여하기

2.1 조건 로직 대 객체 탐색

각 정책에 조건 분기가 흩어짐 → 일관 X.

조건을 객체로 분리하면 일관성 ↑.

2.2 캡슐화 다시 살펴보기

각 정책의 변경 (조건 추가·기준 변경) 이 한 곳에 캡슐화돼야.


3. 일관성 있는 기본 정책 구현하기

3.1 변경 분리하기

각 정책의 "변하는 부분" 과 "안 변하는 부분" 분리: - 안 변하는 부분: "조건 만족 시 요금 계산" 알고리즘 골격. - 변하는 부분: 조건·요금 기준.

3.2 변경 캡슐화하기

변하는 부분을 객체 (정책·조건) 로 캡슐화.

3.3 협력 패턴 설계하기

Phone → RatePolicy.calculateFee()
       각 Call 에 대해 조건 평가
       조건 만족 시 비용 계산
       총합 반환

→ 모든 정책이 같은 협력 흐름.

3.4 추상화 수준에서 협력 패턴 구현하기

public abstract class BasicRatePolicy implements RatePolicy {
    @Override
    public Money calculateFee(Phone phone) {
        Money result = Money.ZERO;
        for (Call call : phone.calls()) {
            result = result.plus(calculateCallFee(call));
        }
        return result;
    }

    protected abstract Money calculateCallFee(Call call);   // 자식이 구현
}

→ Template Method 패턴.

3.5 구체적인 협력 구현하기

public class FixedFeePolicy extends BasicRatePolicy {
    private final Money amount;
    @Override
    protected Money calculateCallFee(Call call) {
        return amount.times(call.seconds());
    }
}

public class TimeOfDayPolicy extends BasicRatePolicy {
    private final List<TimeRule> rules;
    @Override
    protected Money calculateCallFee(Call call) {
        for (TimeRule rule : rules) {
            if (rule.applies(call)) return rule.amount().times(call.seconds());
        }
        return Money.ZERO;
    }
}

3.6 협력 패턴에 맞추기

새 정책 (예: VIP 정책) 추가 = extends BasicRatePolicy + calculateCallFee 구현. 일관된 협력.

3.7 패턴을 찾아라

이렇게 정형화된 협력 패턴 = 디자인 패턴 의 후보. 15장에서 GoF 카탈로그.


핵심 교훈

  1. 같은 협력이 반복 → 일관된 패턴 정형화.
  2. 변경 분리 + 캡슐화 — 안 변하는 부분 + 변하는 부분 분리.
  3. Template Method = 일관된 협력 패턴의 정형.
  4. 새 정책 = 새 클래스 — OCP 충족.
  5. 패턴은 발견 되는 것 — 처음부터 의도하지 마라.

현업 예제 — Spring 의 일관된 협력

Repository 패턴

public interface CrudRepository<T, ID> {
    T save(T entity);
    Optional<T> findById(ID id);
    void deleteById(ID id);
}

public interface OrderRepository extends CrudRepository<Order, Long> { ... }
public interface UserRepository extends CrudRepository<User, Long> { ... }

→ 모든 Repository 가 같은 협력 패턴. 새 엔티티 = 새 Repository 인터페이스 1개.

Spring Security 인증 체인

public interface AuthenticationProvider {
    Authentication authenticate(Authentication a);
}

→ JWT·OAuth·Form·LDAP 모두 같은 협력 패턴. 새 인증 방식 = 새 Provider 1개.


함정 / 주의

  • 패턴을 처음부터 의도 = 추측성 일반화. 반복 발견 후 정형화.
  • Template Method 의 한계 — 알고리즘 골격 변경 시 모든 자식 영향.
  • 일관성 도그마 — 정말 다른 협력을 같은 패턴으로 강제하면 부자연.

체크리스트

  • 같은 협력 구조가 3+ 곳에 반복되는가 (패턴 후보)
  • 변하는 부분과 안 변하는 부분이 명확히 분리됐는가
  • 새 변형 추가가 한 클래스로 끝나는가 (OCP)
  • 모든 변형이 같은 인터페이스 (RatePolicy) 인가

퀴즈

  1. 일관된 협력 패턴 의 정의?
  2. 핸드폰 과금 4 방식을 일관된 협력으로 통합한 방법?
  3. Template Method 가 이 장의 패턴 정형인 이유?
  4. 새 정책 추가가 한 클래스로 끝나는 이유?
  5. 14장이 15장 (디자인 패턴) 의 전제인 이유?

정답·해설

  1. 같은 객체 협력 구조 (메시지 흐름) 가 여러 변형에 일관 적용 되는 것. 변하는 부분 (조건·기준) 만 자식이 책임지고, 안 변하는 부분 (협력 흐름) 은 공통.
  2. 변경 분리 — "조건 평가 + 요금 계산" 알고리즘 골격 = BasicRatePolicy.calculateFee(), 변하는 부분 = calculateCallFee() 추상 메서드. 각 정책은 calculateCallFee 만 구현.
  3. 알고리즘 골격 (변하지 않음) = 부모 + 단계 구현 (변함) = 자식 의 정확한 정형. 일관된 협력 = 골격 공유 + 단계 분기. Template Method 는 이를 명시한 패턴.
  4. 추상에 의존 + 다형성. 부모 클래스 (BasicRatePolicy) 가 추상 메서드 (calculateCallFee) 를 통해 자식과 협력. 새 정책 = 새 자식 클래스 + 메서드 1개 구현. 부모·기존 자식 무변경. OCP.
  5. 반복 발견된 패턴이 곧 디자인 패턴. 14장이 "패턴이 자연 발견되는 흐름" 을 보여주고, 15장이 GoF 의 정형화된 카탈로그 (Template Method·Strategy·State 등) 를 소개. 14장이 없으면 15장이 외울 카탈로그로 보임.

다음 장 예고 — 15장: 디자인 패턴과 프레임워크

14장의 패턴 발견을 GoF 디자인 패턴 카탈로그 로 체계화. 패턴과 책임 주도 설계, 캡슐화와 디자인 패턴, 프레임워크 의 코드 재사용 vs 설계 재사용. 오브젝트 의 마지막 본문.