이 책의 마지막 장은 새로운 기법을 가르치지 않는다. 대신 지금까지 우리가 손으로 밟아 온 설계들이 사실은 이름 붙은 디자인 패턴이었음을 되짚는다. 패턴을 먼저 배우고 코드에 적용한 게 아니라, 좋은 설계를 향해 리팩터링하다 보니 자연히 패턴에 도달한 것이다. 이것이 이 책이 패턴을 마지막에 두는 이유다 — 패턴은 출발점이 아니라 도착점의 이름이다.

패턴은 설계의 재사용이다

디자인 패턴은 반복되는 설계 문제에 대한 검증된 해법이자, 그 해법을 부르는 공통 어휘다. 코드를 복사하는 게 아니라 구조를 재사용한다. “여기에 STRATEGY를 쓰자”는 한마디가 클래스 다이어그램 한 장을 대신한다.

이 책의 예제들은 이미 세 가지 패턴을 지나왔다.

TEMPLATE METHOD — 10장. 상속으로 핸드폰 과금을 구현했을 때, 부모가 알고리즘의 뼈대를 고정하고 변하는 한 걸음만 자식에게 맡겼다.

public abstract class BasicRatePolicy {
    public Money calculateFee(Phone phone) {   // 불변의 뼈대 = 템플릿 메서드
        Money result = Money.ZERO;
        for (Call call : phone.getCalls())
            result = result.plus(calculateCallFee(call));
        return result;
    }
    protected abstract Money calculateCallFee(Call call);   // 변하는 한 걸음만 자식이
}

순회하며 합산하는 절차는 부모가 쥐고, calculateCallFeeRegularPolicy·NightlyDiscountPolicy가 다르게 채운다. 이것이 TEMPLATE METHOD다. 자세한 전개는 10장에 있다.

STRATEGY — 11장. 상속을 합성으로 바꾸자, 변하는 알고리즘이 RatePolicy라는 별도 객체가 되어 Phone에 주입됐다.

public class Phone {
    private RatePolicy ratePolicy;   // 알고리즘을 객체로 캡슐화해 갈아 끼운다
    public Money calculateFee() {
        return ratePolicy.calculateFee(this);
    }
}

TEMPLATE METHOD가 상속으로 묶었던 “변하는 부분”을, STRATEGY는 합성으로 분리해 런타임에 교체 가능하게 만든다. 같은 문제의 상속판과 합성판이 각각 두 패턴의 이름을 갖는 셈이다.

DECORATOR — 11장. 세금·추가 할인 같은 부가 정책은 기본 정책을 감싸며 책임을 덧입혔다.

public abstract class AdditionalRatePolicy implements RatePolicy {
    private RatePolicy next;   // 다른 정책을 감싼다
    public Money calculateFee(Phone phone) {
        return afterCalculated(next.calculateFee(phone));   // 감싼 결과에 책임을 덧댄다
    }
    protected abstract Money afterCalculated(Money fee);
}

TaxablePolicyRateDiscountablePolicy를 감싸고, 그것이 다시 RegularPolicy를 감싼다. 상속으로는 조합의 폭발을 감당할 수 없던 것을, DECORATOR는 객체를 겹겹이 쌓아 유연하게 조합한다. 합성으로의 전환과 데코레이터 구조는 11장에서 자세히 다뤘다.

프레임워크는 코드의 재사용이고, 제어가 역전된다

패턴이 설계를 재사용한다면, 프레임워크는 코드 자체를 재사용한다. 라이브러리와 프레임워크의 차이는 “누가 누구를 부르는가”에 있다.

  • 라이브러리: 내 코드가 라이브러리를 부른다. 흐름의 주도권은 내게 있다.
  • 프레임워크: 프레임워크가 내 코드를 부른다. 흐름의 주도권은 프레임워크에 있다.

이 뒤바뀜을 **제어의 역전(Inversion of Control)**이라 부른다. “우리를 부르지 마세요, 우리가 당신을 부르겠습니다(Hollywood Principle).” 프레임워크는 애플리케이션의 뼈대와 흐름을 고정해 두고, 변하는 지점(확장점)만 우리에게 구현하도록 남긴다. 사실 이것은 TEMPLATE METHOD를 애플리케이션 규모로 키운 것이다 — 프레임워크가 calculateFee 같은 불변의 흐름을 쥐고, 우리는 calculateCallFee 같은 훅만 채운다.

판단 기준: 흐름의 주도권을 넘겨줄 만큼 문제 영역이 안정적으로 반복되면 프레임워크가 이득이다. 함정: 아직 흐름이 불안정한데 프레임워크부터 세우면, 고정돼야 할 뼈대가 계속 흔들려 오히려 확장점이 족쇄가 된다. 프레임워크도 유사 사례가 쌓인 뒤 추출하는 게 안전하다 — 14장의 교훈과 같다.

절차에서 책임으로, 책임에서 협력으로

이 책 전체를 한 문장으로 되짚으면, 그것은 관점의 이동이었다.

  • 2장의 영화 예매는 객체지향답게 시작했다가, 4장에서 데이터 중심으로 일부러 망가졌다. 데이터를 먼저 정하고 절차가 그것을 주무르는 설계는, 변경이 여기저기로 번졌다.
  • 5장의 책임 주도 설계는 질문을 뒤집었다. “이 데이터로 무엇을 하지”가 아니라 “이 책임은 누가 져야 하지”. 데이터가 아니라 책임이 객체를 나누는 기준이 되자, 변경이 한 객체 안에 갇혔다.
  • 그리고 상속(10장)에서 합성(11장)으로, 다시 일관성 있는 협력(14장)으로 나아가며, 설계의 초점은 개별 객체에서 객체들 사이의 협력으로 옮겨 갔다.

절차 → 책임 → 협력. 좋은 객체지향 설계는 똑똑한 객체 하나를 만드는 일이 아니라, 적절한 책임을 나눠 가진 객체들이 매끄럽게 협력하게 만드는 일이었다. 디자인 패턴은 그 협력의 반복되는 좋은 형태에 붙인 이름이고, 프레임워크는 그 좋은 협력을 코드로 굳혀 재사용하는 것이다. 우리가 패턴을 향해 리팩터링한 게 아니라, 책임과 협력을 좇다 보니 패턴에 도착했다 — 이 책이 처음부터 끝까지 하려던 말이 바로 그것이다.

정리

  • 패턴은 설계(구조)의 재사용이자 공통 어휘다. 코드를 복사하지 않고 검증된 구조를 재사용한다.
  • 이 책의 예제들은 이미 패턴이었다 — 10장의 상속은 TEMPLATE METHOD, 11장의 합성은 STRATEGY와 DECORATOR.
  • 프레임워크는 코드의 재사용이며, 흐름의 주도권이 프레임워크로 넘어가는 제어의 역전이 핵심이다. TEMPLATE METHOD를 애플리케이션 규모로 키운 것이다.
  • 패턴도 프레임워크도 유사 사례가 쌓인 뒤 추출하는 게 안전하다. 선제적 적용은 과설계이자 족쇄다.
  • 책 전체의 여정은 절차 → 책임 → 협력이었다. 좋은 설계는 똑똑한 객체가 아니라 잘 나뉜 책임들의 매끄러운 협력이다.