"디자인 패턴은 협력 템플릿을, 프레임워크는 코드 템플릿을 제공한다."
이 장에서는 디자인 패턴과 프레임워크를 다룹니다. 반복적으로 발생하는 문제를 해결하기 위한 검증된 설계 지식을 어떻게 재사용할 수 있는지, 그리고 설계와 코드를 함께 재사용하는 방법을 학습합니다.
- 디자인 패턴의 본질과 가치 이해하기
- 패턴 분류 체계 파악하기 (아키텍처, 디자인, 이디엄, 분석)
- 주요 디자인 패턴의 목적과 구조 학습하기
- 프레임워크의 개념과 특징 이해하기
- 제어 역전 원리의 의미 파악하기
- 설계 재사용과 코드 재사용의 조화 이해하기
Martin Fowler의 정의:
패턴 정의는
하나의 실무 컨텍스트(practical context)에서
유용하게 사용해왔고
다른 실무 컨텍스트에서도
유용할 것이라고 예상되는
아이디어(idea)다
핵심 특징:
1. 반복적으로 발생하는 문제와 해법의 쌍
2. 알려진 문제와 해법을 문서화
→ 지식 전달 가능
3. 추상적 원칙과 실제 코드 사이의 간극을 메움
→ 실질적인 코드 작성 돕기
4. 실무 경험에서 비롯됨
→ 검증된 해결책
패턴으로 인정받기 위한 조건:
최소 3가지의 서로 다른 시스템에
특별한 문제 없이 적용할 수 있고
유용한 경우에만 패턴으로 간주
→ 경험을 통한 검증 필요
1. 경험의 효과적 전달
패턴 = 경험의 산물
실무 경험이 적은 초보자도:
→ 패턴을 익히고 반복 적용
→ 유연하고 품질 높은 소프트웨어 개발 방법 습득
2. 공통 어휘 제공
❌ 패턴을 모르는 경우:
"인터페이스를 하나 추가하고
이 인터페이스를 구체화하는 클래스를 만든 후
객체의 생성자나 setter 메서드에 할당해서
런타임 시에 알고리즘을 바꿀 수 있게 하자"
✅ 패턴을 아는 경우:
"STRATEGY 패턴을 적용하자"
→ 높은 수준의 의사소통 가능!
3. 추상 원칙의 구체화
추상적인 설계 원칙
↓
패턴 (구체적 템플릿)
↓
실제 코드
→ 간극을 메워주는 다리 역할
┌─────────────────────────────────────────┐
│ 아키텍처 패턴 │
│ (Architecture Pattern) │
│ - 소프트웨어 전체 구조 │
│ - 서브시스템 정의 │
│ - MVC, Layered Architecture │
└─────────────┬───────────────────────────┘
│
┌─────────────▼───────────────────────────┐
│ 디자인 패턴 │
│ (Design Pattern) │
│ - 협력하는 컴포넌트 구조 │
│ - STRATEGY, TEMPLATE METHOD │
│ - DECORATOR, COMPOSITE │
└─────────────┬───────────────────────────┘
│
┌─────────────▼───────────────────────────┐
│ 이디엄 │
│ (Idiom) │
│ - 특정 언어에 국한 │
│ - COUNT POINTER (C++) │
└─────────────────────────────────────────┘
분석 패턴 (Analysis Pattern)
┌────────────────────────────┐
│ 도메인 개념적 문제 해결 │
│ 업무 모델링 구조 │
└────────────────────────────┘
정의:
소프트웨어 전체 구조를 결정하기 위해 사용
제공하는 것:
- 미리 정의된 서브시스템
- 서브시스템의 책임 정의
- 서브시스템 간 관계 조직화 규칙
예시:
MVC (Model-View-Controller)
Layered Architecture (계층형 아키텍처)
Microservices Architecture
Event-Driven Architecture
특징:
범위: 시스템 전체
적용 단계: 아키텍처 설계
영향: 전체 시스템 구조
정의:
특정 상황 내에서
일반적인 설계 문제를 해결하며
협력하는 컴포넌트들 사이에서
반복적으로 발생하는 구조를 서술
특징:
범위: 컴포넌트 간 협력
적용 단계: 상세 설계
언어 독립적: 프로그래밍 언어나 패러다임에 독립적
예시:
생성 패턴:
- FACTORY METHOD
- ABSTRACT FACTORY
- SINGLETON
구조 패턴:
- ADAPTER
- DECORATOR
- COMPOSITE
행위 패턴:
- STRATEGY
- TEMPLATE METHOD
- OBSERVER
정의:
특정 프로그래밍 언어에만 국한된 하위 레벨 패턴
주어진 언어의 기능을 사용해
컴포넌트 간 특정 측면을 구현하는 방법 서술
예시: COUNT POINTER (C++)
// C++의 이디엄
class CountPointer {
int* ptr;
int* refCount;
CountPointer(int* p) : ptr(p), refCount(new int(1)) {}
~CountPointer() {
if (--(*refCount) == 0) {
delete ptr;
delete refCount;
}
}
};특징:
언어 종속적:
- C++ COUNT POINTER 이디엄
- Java에서는 불필요 (가비지 컬렉터)
- Python의 컨텍스트 매니저 (with 문)
- Go의 defer 패턴
정의:
도메인 내의 개념적 문제를 해결하는 데 초점
업무 모델링 시 발견되는
공통적인 구조를 표현하는 개념들의 집합
특징:
기술적 문제 X
도메인 개념적 문제 O
예시:
- Account Transaction 패턴
- Party 패턴 (사람/조직 모델링)
- Inventory 패턴
비교:
| 패턴 종류 | 범위 | 목적 | 예시 |
|---|---|---|---|
| 아키텍처 | 시스템 전체 | 전체 구조 결정 | MVC, Layered |
| 디자인 | 컴포넌트 협력 | 설계 문제 해결 | STRATEGY, DECORATOR |
| 이디엄 | 언어 수준 | 언어 특화 구현 | COUNT POINTER |
| 분석 | 도메인 | 개념적 문제 해결 | Account Transaction |
┌─────────────────────────────────────────────────────┐
│ │
│ 패턴의 구성 요소는 클래스가 아니라 '역할'이다! │
│ │
└─────────────────────────────────────────────────────┘
의미:
역할은 추상적 개념
→ 다양한 방법으로 구현 가능
1. 하나의 객체가 여러 역할 수행 가능
2. 다수의 클래스가 동일한 역할 구현 가능
3. 구현 방법에 제한 없음
예시:
STRATEGY 패턴:
역할:
- Context (컨텍스트)
- Strategy (전략)
- ConcreteStrategy (구체 전략)
구현 선택:
- Strategy를 인터페이스로? 추상 클래스로?
- ConcreteStrategy 개수는?
- Context가 Strategy를 어떻게 사용?
→ 역할과 책임만 유사하면
다양한 구현 가능!
디자인 패턴을 따른다 =
특정 구현 방식 강제 X
역할, 책임, 협력 관점에서
유사성을 공유한다 O
→ 창의적 적용 가능!
역할 기반 패턴 적용:
1. 패턴이 제시하는 역할 이해
↓
2. 현재 문제의 역할 식별
↓
3. 역할 간 책임 할당
↓
4. 협력 설계
↓
5. 구체적 구현 (다양한 방법 가능)
핵심:
모든 디자인 패턴의 목적:
→ 변경을 캡슐화하는 것
패턴마다 캡슐화하는 변경이 다름
목적: 알고리즘의 변경을 캡슐화
구조:
┌──────────────┐
│ Context │
│ │
│ + operation()│
└──────┬───────┘
│ uses
│
▼
┌───────────────┐
│ <<interface>>│
│ Strategy │
│ │
│ + algorithm() │
└──────┬────────┘
△
│
┌───┴───┬───────────┐
│ │ │
┌──▼───┐ ┌─▼────┐ ┌───▼──┐
│Strat │ │Strat │ │Strat │
│egy A │ │egy B │ │egy C │
└──────┘ └──────┘ └──────┘
영화 예매 시스템 예시:
// Context
public class Movie {
private DiscountPolicy discountPolicy; // Strategy
public Money calculateMovieFee(Screening screening) {
// 알고리즘 실행 위임
return fee.minus(discountPolicy.calculateDiscountAmount(screening));
}
}
// Strategy
public abstract class DiscountPolicy {
abstract protected Money getDiscountAmount(Screening screening);
}
// ConcreteStrategy
public class AmountDiscountPolicy extends DiscountPolicy {
@Override
protected Money getDiscountAmount(Screening screening) {
return discountAmount;
}
}
public class PercentDiscountPolicy extends DiscountPolicy {
@Override
protected Money getDiscountAmount(Screening screening) {
return screening.getMovieFee().times(percent);
}
}특징:
✅ 객체 합성 사용
✅ 런타임에 알고리즘 변경 가능
✅ 낮은 결합도
✅ 높은 유연성
변경 캡슐화:
- 알고리즘의 종류
- 알고리즘의 구현 방법
목적: 알고리즘의 변경을 캡슐화 (상속 사용)
구조:
┌──────────────────────┐
│ AbstractClass │
│ │
│ + templateMethod() │
│ # primitiveOp1() │
│ # primitiveOp2() │
└──────────┬───────────┘
△
│
┌─────┴─────────│───┐
│ │
┌────▼─────────┐ ┌───▼──────────┐
│Concrete │ │Concrete │
│Class A │ │Class B │
│ │ │ │
│primitiveOp1()│ │primitiveOp2()│
│primitiveOp2()│ │primitiveOp1()│
└──────────────┘ └──────────────┘
영화 예매 시스템 TEMPLATE METHOD 버전:
// AbstractClass
public abstract class Movie {
private Money fee;
// Template Method
public Money calculateFee(Screening screening) {
return fee.minus(calculateDiscountAmount(screening));
}
// Primitive Operation (추상 메서드)
abstract protected Money calculateDiscountAmount(Screening screening);
}
// ConcreteClass
public class AmountDiscountMovie extends Movie {
private Money discountAmount;
@Override
protected Money calculateDiscountAmount(Screening screening) {
return discountAmount;
}
}
public class PercentDiscountMovie extends Movie {
private double percent;
@Override
protected Money calculateDiscountAmount(Screening screening) {
return getMovieFee().times(percent);
}
}특징:
✅ 상속 사용
✅ 컴파일 타임에 알고리즘 결정
✅ STRATEGY보다 단순
❌ 런타임 변경 불가
❌ STRATEGY보다 높은 결합도
변경 캡슐화:
- 알고리즘의 세부 단계
- 단계의 구체적 구현
| 측면 | STRATEGY | TEMPLATE METHOD |
|---|---|---|
| 기법 | 객체 합성 | 상속 |
| 변경 시점 | 런타임 | 컴파일 타임 |
| 결합도 | 낮음 | 높음 |
| 복잡도 | 높음 | 낮음 |
| 유연성 | 높음 | 낮음 |
선택 기준:
STRATEGY 선택:
- 런타임 알고리즘 변경 필요
- 알고리즘을 독립적으로 재사용
- 낮은 결합도 중요
TEMPLATE METHOD 선택:
- 알고리즘이 고정적
- 단순한 구현 선호
- 코드 중복 제거가 주 목적
목적: 선택적 행동의 순서와 개수 변경을 캡슐화
구조:
┌──────────────┐
│ <<interface>>│
│ Component │
│ │
│ + operation()│
└──────┬───────┘
△
│
┌───┴────────────────┐
│ │
┌──▼────────┐ ┌──────▼───────┐
│Concrete │ │ Decorator │
│Component │ │ │
└───────────┘ │ - component │
│ + operation()│
└──────┬───────┘
△
│
┌─────┴────┐
│ │
┌──────▼───┐ ┌────▼──────┐
│Concrete │ │Concrete │
│DecoratorA│ │DecoratorB │
└──────────┘ └───────────┘
핸드폰 과금 시스템 예시:
// Component
public interface RatePolicy {
Money calculateFee(Phone phone);
}
// ConcreteComponent
public abstract class BasicRatePolicy implements RatePolicy {
@Override
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);
}
// Decorator
public abstract class AdditionalRatePolicy implements RatePolicy {
private RatePolicy next; // 감싸질 객체
public AdditionalRatePolicy(RatePolicy next) {
this.next = next;
}
@Override
public Money calculateFee(Phone phone) {
Money fee = next.calculateFee(phone);
return afterCalculated(fee);
}
abstract protected Money afterCalculated(Money fee);
}
// ConcreteDecorator
public class TaxablePolicy extends AdditionalRatePolicy {
private double taxRatio;
public TaxablePolicy(double taxRatio, RatePolicy next) {
super(next);
this.taxRatio = taxRatio;
}
@Override
protected Money afterCalculated(Money fee) {
return fee.plus(fee.times(taxRatio));
}
}
public class RateDiscountablePolicy extends AdditionalRatePolicy {
private Money discountAmount;
public RateDiscountablePolicy(Money discountAmount, RatePolicy next) {
super(next);
this.discountAmount = discountAmount;
}
@Override
protected Money afterCalculated(Money fee) {
return fee.minus(discountAmount);
}
}사용 예:
// 일반 요금제
Phone phone = new Phone(
new RegularPolicy(...)
);
// 일반 요금제 + 세금
Phone phone = new Phone(
new TaxablePolicy(0.05,
new RegularPolicy(...)
)
);
// 일반 요금제 + 세금 + 요금 할인
Phone phone = new Phone(
new RateDiscountablePolicy(Money.wons(1000),
new TaxablePolicy(0.05,
new RegularPolicy(...)
)
)
);
// 순서 변경 가능!
Phone phone = new Phone(
new TaxablePolicy(0.05,
new RateDiscountablePolicy(Money.wons(1000),
new RegularPolicy(...)
)
)
);특징:
✅ 런타임에 행동 동적 추가
✅ 행동의 순서 변경 가능
✅ 행동의 개수 제한 없음
✅ 단일 책임 원칙 (각 데코레이터는 하나의 책임)
변경 캡슐화:
- 선택적 행동의 종류
- 행동의 조합 순서
- 행동의 개수
| 패턴 | 캡슐화 대상 | 변경 시점 | 기법 |
|---|---|---|---|
| STRATEGY | 알고리즘 종류 | 런타임 | 합성 |
| TEMPLATE METHOD | 알고리즘 단계 | 컴파일 | 상속 |
| DECORATOR | 행동의 순서/개수 | 런타임 | 합성 |
┌─────────────────────────────────────────────────────┐
│ │
│ 패턴은 출발점이지 목적지가 아니다! │
│ │
│ 패턴이 목표가 되어서는 안 된다 │
│ │
└─────────────────────────────────────────────────────┘
초보자의 함정:
패턴의 강력함에 매료됨
↓
모든 설계에 패턴 적용 시도
↓
명확한 트레이드오프 없이 남용
↓
불필요하게 복잡한 설계
1. 문제부터 시작
❌ 나쁜 순서:
패턴 선택 → 문제에 적용
✅ 좋은 순서:
문제 분석 → 적합한 패턴 탐색
2. 트레이드오프 고려
패턴 적용 전 질문:
1. 이 패턴이 정말 필요한가?
2. 더 단순한 해결책은 없는가?
3. 복잡도 증가가 정당한가?
4. 팀이 이해할 수 있는가?
3. 목적에 맞게 수정
패턴을 맹목적으로 따르지 말고
현재 요구사항과 기술에 맞게 수정
패턴 = 가이드라인
≠ 엄격한 규칙
패턴을 알고 있다면:
현재 문제에 딱 들어맞지 않더라도
참조할 수 있는 모범적
역할과 책임의 집합을 알고 있는 것
→ 큰 도움이 된다!
활용 방법:
1. 비슷한 문제 식별
↓
2. 패턴 구조 참조
↓
3. 현재 상황에 맞게 조정
↓
4. 역할과 책임 재배치
↓
5. 새로운 협력 패턴 창출
✅ 해결하려는 문제가 명확한가?
✅ 패턴이 문제 해결에 적합한가?
✅ 트레이드오프를 이해했는가?
✅ 팀이 패턴을 이해하는가?
✅ 유지보수 비용을 고려했는가?
✅ 더 단순한 대안을 검토했는가?
모두 YES → 패턴 적용 고려
하나라도 NO → 재검토 필요
┌─────────────────────────────────────────────────────┐
│ │
│ 정당한 이유 없이 사용된 모든 패턴은 │
│ 설계를 복잡하게 만드는 장애물이다 │
│ │
│ 패턴은 비싸다 │
│ 반드시 그 가치가 있을 때만 사용이 정당화된다 │
│ │
└─────────────────────────────────────────────────────┘
설계 재사용:
장점:
✅ 추상적 수준에서 재사용
✅ 다양한 상황 적용 가능
✅ 검증된 구조
단점:
❌ 매번 유사한 코드 작성
❌ 개발 시간 증가
❌ 일관성 유지 어려움
코드 재사용:
장점:
✅ 빠른 개발
✅ 검증된 구현
✅ 일관성 보장
단점:
❌ 특정 상황에만 적용
❌ 유연성 부족
❌ 커스터마이징 어려움
┌─────────────────────────────────────────────────────┐
│ │
│ 가장 이상적인 재사용: │
│ 설계 재사용 + 코드 재사용을 적절히 조합 │
│ │
│ → 프레임워크! │
│ │
└─────────────────────────────────────────────────────┘
구조적 측면:
추상 클래스나 인터페이스를 정의하고
인스턴스 사이의 상호작용을 통해
시스템 전체 혹은 일부를 구현해 놓은
재사용 가능한 설계
목적 측면:
애플리케이션 개발자가
현재의 요구사항에 맞게
커스터마이징할 수 있는
애플리케이션의 골격(skeleton)
1. 애플리케이션 아키텍처
전체 시스템의 구조와
주요 컴포넌트 정의
예:
- MVC 구조
- 계층형 아키텍처
- 이벤트 기반 아키텍처
2. 기반 코드
문제 해결에 필요한
설계 결정과 구현 코드
예:
- HTTP 요청 처리
- 데이터베이스 연결
- 트랜잭션 관리
3. 확장 가능한 추상 클래스와 인터페이스
부분적으로 구현된 클래스들
개발자가 상속/구현해서
애플리케이션 로직 추가
4. 재사용 가능한 컴포넌트
추가 작업 없이 바로 사용 가능한
다양한 종류의 컴포넌트
예:
- 로깅 컴포넌트
- 캐싱 컴포넌트
- 보안 컴포넌트
프레임워크는 코드를 재사용함으로써
설계 아이디어를 재사용한다
설계 자체의 재사용을 중요시한다
예시: 핸드폰 과금 시스템을 프레임워크로
프레임워크 제공:
- RatePolicy 인터페이스
- BasicRatePolicy 추상 클래스
- AdditionalRatePolicy 추상 클래스
- Phone 클래스
- Call 클래스
개발자 구현:
- 구체적인 BasicRatePolicy (고정요금, 시간대별 등)
- 구체적인 AdditionalRatePolicy (세금, 할인 등)
→ 핵심 협력 구조는 프레임워크가 제공!
프레임워크 = 여러 애플리케이션에서 재사용
→ 변하는 것과 변하지 않는 것을
'배포 단위'로 분리 필요!
변하지 않는 부분 (상위 정책):
billing.framework 패키지
- RatePolicy (인터페이스)
- BasicRatePolicy (추상 클래스)
- AdditionalRatePolicy (추상 클래스)
- Phone
- Call
→ 프레임워크로 배포
→ 안정적
→ 재사용 가능
변하는 부분 (하위 정책):
billing.policies 패키지
- RegularPolicy
- NightlyDiscountPolicy
- TaxablePolicy
- RateDiscountablePolicy
→ 애플리케이션별로 구현
→ 변경 가능
→ 상위 정책에 의존
┌──────────────────────────────┐
│ billing.policies │
│ (하위 정책 - 애플리케이션) │
│ │
│ - RegularPolicy │
│ - NightlyDiscountPolicy │
│ - TaxablePolicy │
└────────────┬─────────────────┘
│ depends on
│
▼
┌──────────────────────────────┐
│ billing.framework │
│ (상위 정책 - 프레임워크) │
│ │
│ - RatePolicy │
│ - BasicRatePolicy │
│ - AdditionalRatePolicy │
│ - Phone │
│ - Call │
└──────────────────────────────┘
핵심:
세부 사항(하위)이 추상화(상위)에 의존!
= 의존성 역전 원칙 (DIP)
상위 정책 패키지가 충분히 안정적이면:
1. 하위 정책 패키지로부터 완벽히 분리
2. 별도의 배포 단위로 만듦
3. 여러 애플리케이션에서 재사용
→ 프레임워크 탄생!
Robert C. Martin의 말:
┌─────────────────────────────────────────────────────┐
│ │
│ "좋은 객체지향 설계의 증명이 바로 의존성의 역전이다" │
│ │
│ "그 의존성이 역전돼 있지 않다면 │
│ 절차적 설계를 갖는 것이다" │
│ │
└─────────────────────────────────────────────────────┘
의존성 역전:
전통적 설계 (하향식):
상위 모듈 → 하위 모듈 의존
객체지향 설계 (의존성 역전):
상위 모듈 ← 하위 모듈 의존
↑
추상화
정의:
의존성을 역전시키면
제어 흐름의 주체도 역전된다!
제어 역전 원리
= Hollywood 원리
"Don't call us, we'll call you"
전통적 방식 (라이브러리):
개발자 코드가 주체
main() {
Library lib = new Library();
lib.method1(); // 개발자가 호출
lib.method2(); // 개발자가 호출
}
→ 개발자가 흐름 제어
프레임워크 방식:
프레임워크가 주체
Framework {
hookMethod1(); // 프레임워크가 호출
hookMethod2(); // 프레임워크가 호출
}
개발자는 hook만 구현
→ 프레임워크가 흐름 제어
정의:
프레임워크에서
일반적인 해결책만 제공하고
애플리케이션에 따라 달라질 수 있는
특정한 동작은 비워둔 부분
→ 이렇게 완성되지 않은 채로 남겨진 동작
특징:
1. 프레임워크가 호출 시점 결정
2. 개발자는 구현만 제공
3. 제어는 프레임워크가 가짐
예시: 핸드폰 과금 시스템의 훅
// 프레임워크가 제공
public abstract class BasicRatePolicy implements RatePolicy {
@Override
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);
}
// 개발자가 구현
public class RegularPolicy extends BasicRatePolicy {
@Override
protected Money calculateCallFee(Call call) {
// 구체적인 요금 계산 로직
return amount.times(
call.getDuration().getSeconds() / seconds.getSeconds()
);
}
}라이브러리:
개발자 코드
│
│ calls
▼
라이브러리 코드
제어: 개발자
프레임워크:
프레임워크 코드
│
│ calls
▼
개발자 코드 (훅)
제어: 프레임워크
Phone.calculateFee()
↓
RatePolicy.calculateFee() ← 프레임워크가 제공
↓
BasicRatePolicy.calculateFee() ← 프레임워크가 제공
↓
calculateCallFee() ← 개발자가 구현 (훅)
↓
RegularPolicy.calculateCallFee() ← 개발자 코드 실행
전체 협력 흐름: 프레임워크가 담당
특정 정책 구현: 애플리케이션 개발자가 담당
→ 제어가 프레임워크로 역전!
과거 (라이브러리):
우리가 직접 라이브러리 코드 호출
현재 (프레임워크):
프레임워크가 우리 코드를 호출
→ 제어가 우리에게서 프레임워크로 넘어감!
개발자의 역할 변화:
라이브러리 시대:
능동적 → 필요할 때 호출
프레임워크 시대:
수동적 → 호출될 때까지 대기
┌─────────────────────────────────────────────────────┐
│ │
│ 협력을 제어하는 것은 프레임워크 │
│ │
│ 애플리케이션 개발자의 코드는 │
│ 상위 모듈(프레임워크)에 의해 호출된다 │
│ │
│ 제어가 프레임워크로 넘어갔다 │
│ = 의존성이 역전되었다! │
│ │
└─────────────────────────────────────────────────────┘
상위 정책을 재사용한다는 것 =
도메인에 존재하는 핵심 개념들 사이의
협력 관계를 재사용한다
예시:
핸드폰 과금 도메인의 핵심 개념:
- 요금 정책
- 기본 정책
- 부가 정책
- 통화
- 요금 계산
이들 사이의 협력:
Phone → RatePolicy
BasicRatePolicy → FeeRule
AdditionalRatePolicy → next
→ 이 협력 구조가 프레임워크!
객체지향 설계에서 재사용성은
객체들 사이의 공통적인 협력 흐름으로부터 나온다
역할 사이의 공통적인 협력 흐름이 있다면
다양한 문맥에 맞는 책임을 구현하면서
설계를 재사용할 수 있다
시각화:
공통 협력 흐름 (프레임워크):
Phone → RatePolicy → BasicRatePolicy
문맥별 구현 (애플리케이션):
- 일반 요금제 시스템: RegularPolicy
- 심야 요금제 시스템: NightlyPolicy
- 시간대별 시스템: TimeOfDayPolicy
→ 협력은 재사용하되
구체적 책임은 각자 구현!
┌─────────────────────────────────────────────────────┐
│ │
│ 디자인 패턴: │
│ 특정한 변경을 일관성 있게 다룰 수 있는 │
│ 협력 템플릿 │
│ │
│ 프레임워크: │
│ 특정한 변경을 일관성 있게 다룰 수 있는 │
│ 확장 가능한 코드 템플릿 │
│ │
└─────────────────────────────────────────────────────┘
| 측면 | 디자인 패턴 | 프레임워크 |
|---|---|---|
| 제공 | 협력 템플릿 | 코드 템플릿 |
| 재사용 | 설계 재사용 | 설계 + 코드 재사용 |
| 형태 | 문서, 아이디어 | 실행 가능한 코드 |
| 적용 | 개발자가 구현 | 확장 포인트 구현 |
| 제어 | 개발자 | 프레임워크 (제어 역전) |
패턴 = 반복적 문제 + 검증된 해법
특징:
- 실무 경험에서 비롯
- 3의 규칙 (최소 3곳에서 검증)
- 공통 어휘 제공
- 역할 기반 (클래스 기반 X)
아키텍처 패턴: 시스템 전체 구조
↓
디자인 패턴: 컴포넌트 협력 구조
↓
이디엄: 언어별 구현 방법
분석 패턴: 도메인 개념 구조
STRATEGY:
- 목적: 알고리즘 변경 캡슐화
- 기법: 객체 합성
- 장점: 런타임 변경 가능
TEMPLATE METHOD:
- 목적: 알고리즘 변경 캡슐화
- 기법: 상속
- 장점: 단순한 구조
DECORATOR:
- 목적: 행동 순서/개수 변경 캡슐화
- 기법: 객체 합성
- 장점: 동적 조합
구성:
- 애플리케이션 아키텍처
- 기반 코드
- 추상 클래스/인터페이스
- 재사용 가능한 컴포넌트
원리:
- 의존성 역전 (DIP)
- 제어 역전 (IoC)
- 훅 (Hook)
라이브러리: 개발자가 호출
프레임워크: 프레임워크가 호출
제어의 주체가 역전됨!
✅ Do:
- 문제부터 시작
- 트레이드오프 고려
- 목적에 맞게 수정
- 팀과 공유
❌ Don't:
- 패턴을 목표로 삼기
- 맹목적으로 따르기
- 모든 곳에 적용
- 복잡도 무시
핵심 원칙:
1. 상위 정책과 하위 정책 분리
- 변하지 않는 것: 프레임워크
- 변하는 것: 애플리케이션
2. 의존성 역전 적용
- 하위가 상위에 의존
- 추상화에 의존
3. 적절한 훅 제공
- 확장 포인트 명확히
- 호출 시점 문서화
4. 공통 협력 추출
- 재사용 가능한 구조
- 역할 기반 설계
1️⃣ 따라 하는 수준 (Shu):
- 모방
- 주어진 절차를 따름
- 결과물 얻는 안정감
2️⃣ 분리 수준 (Ha):
- 다양한 절차 학습
- 트레이드오프 이해
- 상황에 맞는 절차 적용
- 판단력과 유연함
3️⃣ 거침없는 수준 (Ri):
- 해법을 직관적으로 떠올림
- 절차를 넘어선 창의성
- 가장 적합한 방법 선택
디자인 패턴 학습
↓
리팩터링 연습
↓
테스트 주도 개발 (TDD)
↓
'분리 수준'으로 성장
↓
실무 경험 축적
↓
'거침없는 수준'으로 진화
거침없는 수준의 설계자:
- 직관적으로 설계 완료
↓ 문서화
분리 수준의 관점:
- 트레이드오프 설명
- 대안 비교
- 결정 이유 제시
↓ 문서화
따라 하는 수준의 사람도 이해:
- 단계별 설명
- 구체적 예시
- 명확한 가이드
┌─────────────────────────────────────────────────────┐
│ │
│ 소프트웨어를 설계하는 │
│ 단 하나의 방법이란 존재하지 않는다 │
│ │
│ 중요한 것은: │
│ 현재의 설계에 맹목적으로 일관성을 맞추는 것이 아니라 │
│ 변경의 방향에 맞춰 │
│ 지속적으로 개선하려는 의지다 │
│ │
│ 디자인 패턴과 프레임워크는 │
│ 그 여정의 훌륭한 동반자다 │
│ │
└─────────────────────────────────────────────────────┘
1. 패턴은 경험의 산물
→ 검증된 해법 활용
2. 패턴은 출발점
→ 맹목적 적용 지양
3. 역할 기반 사고
→ 구현 방법은 다양
4. 변경을 캡슐화
→ 패턴의 목적 이해
5. 프레임워크 = 설계 + 코드
→ 재사용의 최고 형태
6. 제어 역전
→ 프레임워크의 핵심
7. 협력 흐름 재사용
→ 상위 정책의 가치
8. 지속적 개선
→ 완벽한 설계는 없음
- Chapter 14: 일관성 있는 협력 → 패턴으로 구체화
- 전체 책: 역할, 책임, 협력 → 패턴의 기초
- 변경의 캡슐화 → 모든 패턴의 목적
- 다음 단계:
- 디자인 패턴 깊이 학습 (GoF 패턴)
- 리팩터링 실천
- TDD 적용
- 프레임워크 소스 분석
- 오픈소스 기여