✏️, 💡,❓ 해당 이모지는 저의 생각임을 나타냅니다.
소프트웨어 설계에서 반복적으로 발생하는 문제에 대해 적용할 수 있는 해결 방법을 디자인 패턴이라고 한다.
일단 디자인 패턴을 익히면, 변경과 방향과 주기를 이해하는 것만으로도 협력 방식을 순간적으로 떠올릴 수 있게 한다.
디자인 패턴이 설계를 재사용하기 위한 것이라면 프레임워크는 설계와 코드를 함께 재사용하기 위한 것이다. 프레임워크는 애플리케이션의 아키텍처를 구현 코드의 형태로 제공한다.
디자인 패턴은 특정한 변경을 일관성 있게 다룰 수 있는 협력 템플릿을 제공하고, 프레임워크는 특정한 변경을 일관성 있게 다룰 수 있는 확장 가능한 코드 템플릿을 제공한다.
01 디자인 패턴과 설계 재사용
소프트웨어 패턴
패턴이라는 숲 속에서 길을 잃지 안힉 위해서는 패턴의 정의보다는 패턴이라는 용어가 풍기는 미묘한 뉘앙스를 이해하는 것이 중요하다.
- 패턴은 반복적으로 발생하는 문제와 해법의 쌍으로 정의된다.
- 패턴을 사용함으로써 이미 알려진 문제와 해법을 문서로 정리할 수 있으며, 이 지식을 다른 사람과 의사소통 할 수 있다.
- 패턴은 추상적인 원칙과 실제 코드 작성 사이의 간극을 메워주며 실질적인 코드 작성을 돕는다.
패턴은 여러 컨텍스트에서 유용한 '아이디어'다. 3의 규칙(Rule of Three)에 따르면 최소 세 가지의 서로 다른 시스템에 특별한 문제 없이 적용할 수 있고 유용해야 패턴으로 간주할 수 있다.
패턴 분류
- 아키텍처 패턴(Architecture Pattern)
- 분석 패턴 (Analysis Pattern)
- 디자인 패턴 (Design Pattern)
- 이디엄 (Idiom)
디자인 패턴의 상위에는 소프트웨어 전체적인 구조 결정을 위한 아키텍처 패턴이 위치한다.
✏️ 예를 들어 Domain 기반 아키텍처나 계층형 아키텍처 등이 있을 수 있다.
디자인 패턴의 하위에는 이디엄이 위치한다. 이디엄은 특정 프로그래밍 언어에만 국한된 패턴이다. 주어진 언어의 기능을 사용해 컴포넌트 간의 특정 측면을 구현하는 방법을 서술한다.
아키텍처 패턴, 디자인 패턴, 이디엄이 주로 기술적 문제를 해결하는 데 초점을 맞추고 있다면 분석 패턴은 도메인 내의 개념적 문제를 해결하는 데 초점을 맞춘다. 분석 패턴은 업무 모델링 시 발견되는 공통적인 구조를 표현하는 개념들의 집합이다.
패턴과 책임-주도 설계
패턴은 공통적 역할과 책임, 협력의 훌륭한 템플릿을 제공한다.
STRATEGY 패턴은 다양한 알고리즘을 동적으로 교체할 수 있는 역할과 책임의 집합을 제공한다.
BRIDGE 패턴은 추상화의 조합으로 인해 클래스가 폭발적으로 증가하는 문제를 해결하기 위해, 책임을 추상화와 구현으로 분해함으로써 설계를 확장 가능하게 만든다.
OBSERVER 패턴은 객체의 결합도를 낮출 수 있는 역할과 책임의 집합을 제공한다.
패턴의 구성 요소는 클래스가 아니라 '역할'이다.
패턴을 구성하는 요소가 클래스가 아니라 역할이라는 사실은 패턴 템플릿을 구현할 수 있는 다양한 방법이 존재한다는 사실을 암시한다.
- 하나의 객체가 다양한 역할 모두를 수행해도 된다.
- 다수의 클래스가 동일한 역할을 구현할 수도 있다.
어떤 구현 코드가 디자인 패턴을 따른다고 이해할 때는 역할, 책임, 협력 관점에서 유사성을 공유한다는 것이지 특정한 구현 방식을 강제하는 것은 아니다.
캡슐화와 디자인 패턴
앞의 영화 예매 시스템에서 Movie가 DiscountPolicy 상속 계층을 합성 관계로 유지해야 하는 원칙과 이유를 설명했지만 사실 이 설계는 STRATEGY 패턴을 적용한 예다. STRATEGY 패턴의 목적은 알고리즘의 변경을 캡슐화하는 것이고 이를 구현하기 위해 객체 합성을 이용한다.
물론 변경을 캡슐화하는 방법은 상속도 있다. 이처럼 캡슐화를 위해 합성이 아닌 상속을 사용하는 것을 TEMPLATE METHOD 패턴이라고 한다. (변경하지 않는 부분을 부모 클래스로, 변하는 부분은 자식 클래스로)

합성 - 추상 클래스나 인터페이스를 사용해 변경을 캡슐화
상속 - 추상 메서드 사용해 변경을 캡슐화
그림 15.5에서 cacluateDiscountAmount 메서드가 추상 메서드이다. 부모 클래스의 calculateFee 메서드에서 추상 메서드인 calculateDiscountAmount를 호출하고, 자식 클래스들이 구현한다.
TEMPLATE METHOD는 합성보다 결합도가 높아 STRATEGY 패턴처럼 런타임에 객체의 알고리즘을 바꿀 순 없지만 STRATEGY 패턴보다 복잡도가 낮다.
핸드폰 과금 시스템 설계는 DECORATOR 패턴을 기반으로 한다. DECORATOR 패턴은 객체의 행동을 동적으로 추가할 수 있게 해준다. DECORATOR 패턴은 선택적인 행동의 개수와 순서에 대한 변경을 캡슐화 할 수 있다.
패턴은 출발점이다
패턴은 출발점이지 목적지가 아니다. 패턴이 목표가 되서는 안되고 목적에 맞게 패턴을 수정하라. (패턴 만능주의)
패턴을 알고 있다면 현재 문제에 딱 들어맞지 않더라도 참조할 수 있는 모범적 역할과 책임의 집합을 알고 있는 것이니 큰 도움이 된다.
02 프레임워크와 코드 재사용
코드 재사용 대 설계 재사용
재사용 관점에서 설계 재사용보다 더 좋은 방법은 코드 재사용이다. 오랫동안 개발자들은 별도 프로그래밍 없이 기존 컴포넌트를 조립해서 애플리케이션을 구축하는 방법을 추구해왔다. (그러나 도메인은 다양하기 때문에 현실적으로는 아주 적은 부분만 일반화 될 수 있다.)
프레임워크는 애플리케이션 개발자가 현재의 요구사항에 맞게 커스터마이징 할 수 있는 애플리케이션의 골격을 의미한다.
프레임워크는 코드를 재사용함으로써 설계 아이디어를 재사용한다.
: 애플리케이션 아키텍처와 기반 코드를 함께 포함하며, 애플리케이션 확장을 위해 부분적으로 구현된 클래스와 인터페이스 집합, 바로 사용 가능한 다양한 종류의 컴포넌트 등을 제공한다.
상위 정책과 하위 정책으로 패키지 분리하기
다양한 애플리케이션에서 재사용하기 위해서는 변하는 것과 변하지 않는 것을 분리해야 한다. 프레임워크는 여러 애플리케이션에 걸쳐 재사용 가능해야 하기 때문에 '배포 단위'로 분리해야 한다. 변하는 부분과 변하지 않는 부분을 별도의 패키지로 분리한다.
중요한 것은 패키지 간 의존성 방향이다. 세부 사항을 구현한 패키지는 항상 상위 정책을 구현한 패키지에 의존한다. 이때 상위 정책을 구현하고 있는 패키지가 충분히 안정적이게 되면, 하위 정책 패키지로부터 완벽히 분리해 별도의 배포 단위로 만든다. => 상위 정책 패키지를 여러 애플리케이션에서 재사용할 수 있는 기반이 마련된 것이다.
제어 역전 원리
상위 정책을 재사용한다는 것은, 도메인에 존재하는 핵심 개념들 사이 협력 관계를 재사용 한다는 것이다.
우리는 프레임워크가 적절한 시점에 실행할 것으로 예상되는 코드를 작성할 뿐이다. 제어가 우리에게서 프레임워크로 넘어간 것이다. 우리 코드는 수동적인 존재라 프레임워크가 우리의 코드를 호출해줄 때까지 그저 넋 놓고 기다리고 있을 수밖에 없다.
✏️ 프레임워크가 코드를 호출한다는 것에 의문을 가질 수 있다. 왜냐면 프레임워크는 하위 정책 패키지로부터 완벽히 분리한 별도의 배포 단위니까. 그런데 실행 시점을 보면 이해가 된다. 우리는 main에서 내가 원하는 시점에 라이브러리를 호출할 수 있고, 프레임워크를 사용해서 프레임워크가 애플리케이션을 실행하고 알아서 필요한 메서드를 호출하게 할 수 있다. 프레임워크가 흐름을 제어하는 것이다.
// 내가 만든 프레임워크
public abstract class BatchJob {
public final void run() {
beforeRun();
execute();
afterRun();
}
protected void beforeRun() { }
protected abstract void execute();
protected void afterRun() { }
}
// 하위 애플리케이션
public class MyJob extends BatchJob {
@Override
protected void execute() {
System.out.println("데이터 처리");
}
}
이렇다고 했을 때 execute는 내가 구현한 코드이지만 실제 실행은 BatchJob의 run이 결정한다.
마치며: 나아가기
앨리스터 코어번은 사람이 새로운 기술을 학습하기 위해서 따라 하는 수준, 분리 수준, 거침없는 수준을 거친다고 한다.
따라 하는 수준: 모방, 주어진 절차를 따르면 결과물을 얻을 수 있다는 안정감
분리 수준: 다양한 절차를 학습하고 트레이드 오프. 올바른 절차란 존재하지 않다는 걸 이해하고 각 상황에 따라 적절한 절차를 적용. (판단력과 유연함)
거침없는 수준: 해법을 직관적으로 떠올림. 절차를 트레이드오프하는 수준을 벗어나 가장 적합한 절차를 정함.
소프트웨어를 설계하는 단 하나의 방법이란 존재하지 않는다. 실무자 옆에 앉아 설계가 진행되는 과정을 보더라도 설계자가 찰나에 떠올렸던 트레이드오프 과정을 완벽히 이해할 수 없다. 설계자들은 '거침없는 수준'에서 완료한 작업을 '분리 수준' 관점에서 트레이드 오프 한 후 '따라하는 수준'에 있는 사람들도 이해할 수 있도록 설계 문서를 만든다.
디자인 패턴과 리팩터링, 테스트 주도 개발을 통해 '분리 수준'을 향해 나아가라.
마무리하며
오브젝트가 끝났다. 굉장히 오래걸릴 줄 알았는데 생각보다 짧은 시간이었던 것 같다.
마지막에는 절대적 설계는 없다라는 점을 많이 강조한 것 같다.
그리고 설계자가 거침없는 수준에서 완료한 작업을 따라하는 수준에 있는 사람들도 이해할 수 있도록 문서로서 풀어낸다는 것이 인상깊었다.
조영호님은 참 글을 '따라하는 수준'의 사람들도 알게끔 잘 쓰시는 것 같다.... 정말 객체지향의 사실과 오해 읽을 때도 그렇고 너무너무 좋은 책들이다. 가장 좋은 건 역시 명확하지 않은 개념을 실체화해서 풀어주신다는 것이다. 더 많은 조영호님 책들을 읽어봐야겠다.

'Book' 카테고리의 다른 글
| [오브젝트] 챕터 14: 일관성 있는 협력 (2) | 2025.08.06 |
|---|---|
| [오브젝트] 챕터 13: 서브 클래싱과 서브 타이핑 (2) | 2025.08.05 |
| [오브젝트] 챕터 12: 다형성 - self와 super의 예시 (1) | 2025.07.21 |
| [오브젝트] 챕터 11: 합성과 유연한 설계 (1) | 2025.07.21 |
| [오브젝트] 챕터 10: 상속과 코드 재사용 (1) | 2025.07.21 |