"정적 모델은 동적 모델 위에 세워져야 한다. 행동이 코드를 결정한다."
이 장에서는 동적 모델과 정적 모델의 관계를 다룹니다. 객체의 협력(동적 모델)이 코드 구조(정적 모델)를 주도해야 하며, 도메인 모델은 출발점일 뿐 코드가 협력에 맞춰 진화해야 함을 학습합니다.
- 동적 모델과 정적 모델의 차이 이해하기
- 행동이 코드를 결정하는 과정 학습하기
- 도메인 모델의 올바른 역할 파악하기
- 분석/설계/구현 모델의 통합 이해하기
- TYPE OBJECT 패턴 학습하기
- 협력 중심 설계의 중요성 깨닫기
┌─────────────────────────────────────────────────────┐
│ │
│ 동적 모델 (Dynamic Model): │
│ 프로그램의 실행 구조를 표현하는 움직이는 모델 │
│ │
│ 구성 요소: │
│ - 객체 (Objects) │
│ - 협력 (Collaborations) │
│ - 메시지 (Messages) │
│ - 런타임 행동 (Runtime Behavior) │
│ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ │
│ 정적 모델 (Static Model): │
│ 코드의 구조를 담는 고정된 모델 │
│ │
│ 구성 요소: │
│ - 타입 (Types) │
│ - 관계 (Relationships) │
│ - 클래스 구조 (Class Structure) │
│ - 컴파일 타임 구조 (Compile-time Structure) │
│ │
└─────────────────────────────────────────────────────┘
| 측면 | 동적 모델 | 정적 모델 |
|---|---|---|
| 본질 | 실행 중인 프로그램 | 소스 코드 |
| 초점 | 행동과 협력 | 타입과 관계 |
| 시점 | 런타임 | 컴파일 타임 |
| 표현 | 객체와 메시지 | 클래스와 인터페이스 |
| 변화 | 동적 (계속 변함) | 정적 (고정됨) |
| 목적 | What (무엇을) | How (어떻게) |
┌─────────────────────────────────────────────────────┐
│ │
│ 정적 모델은 동적 모델에 의해 주도되어야 한다 │
│ │
│ 동적 모델 (객체 협력) │
│ ↓ │
│ 정적 모델 (코드 구조) │
│ │
│ 행동이 타입을 결정한다! │
│ │
└─────────────────────────────────────────────────────┘
왜 동적 모델이 우선인가?
1. 소프트웨어의 목적 = 행동 제공
→ 행동이 본질
2. 타입은 행동을 분류하는 도구
→ 수단일 뿐
3. 협력이 설계의 중심
→ 협력에 맞는 타입 필요
4. 변경은 행동에서 시작
→ 행동 변경에 따라 타입 진화
동적 모델:
"사용자가 영화를 예매한다"
"할인 정책에 따라 가격을 계산한다"
"상영 시간을 확인한다"
↓ 영향
정적 모델:
class Movie { ... }
interface DiscountPolicy { ... }
class Screening { ... }
↓ 구현
동적 모델 실현:
new Movie(...).calculateFee(screening)
discountPolicy.calculateDiscount(...)
┌─────────────────────────────────────────────────────┐
│ │
│ 프로그래머는 두 가지 모델을 동시에 그려야 한다 │
│ │
│ 1. 동적 모델 (런타임 협력) │
│ - 객체들이 어떻게 협력하는가? │
│ - 어떤 메시지를 주고받는가? │
│ - 책임이 어떻게 분배되는가? │
│ │
│ 2. 정적 모델 (코드 구조) │
│ - 어떤 클래스가 필요한가? │
│ - 클래스 간 관계는 무엇인가? │
│ - 타입 계층은 어떻게 구성하는가? │
│ │
│ 그리고 동적 모델이 정적 모델을 주도하게 한다! │
│ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ │
│ 가장 중요한 것은 │
│ 객체가 외부에 제공하는 행동이다 │
│ │
│ 행동 → 책임 → 협력 → 타입 → 클래스 │
│ │
└─────────────────────────────────────────────────────┘
순서:
1단계: 행동 파악
"영화 가격을 계산해야 한다"
2단계: 책임 할당
"Movie가 가격 계산 책임"
"DiscountPolicy가 할인 계산 책임"
3단계: 협력 설계
Movie → DiscountPolicy.calculateDiscount()
4단계: 타입 정의
interface DiscountPolicy { ... }
5단계: 클래스 구현
class AmountDiscountPolicy implements DiscountPolicy { ... }
// ❌ 나쁜 접근: 타입부터 생각
// "어떤 클래스가 필요할까?"
class Movie { ... }
class Actor { ... }
class Director { ... }
class Theater { ... }
// 문제:
// - 협력이 없음
// - 책임이 불명확
// - 행동이 정의되지 않음
// - 변경에 취약문제점:
1. 데이터 중심 설계
→ 캡슐화 위반
2. 책임 불명확
→ 응집도 낮음
3. 협력 부재
→ 결합도 높음
4. 변경 어려움
→ 유지보수 곤란
// ✅ 좋은 접근: 행동부터 생각
// "무엇을 해야 하는가?"
// 1. 행동 정의
"영화 예매"
"가격 계산"
"할인 적용"
// 2. 책임 할당
Movie: 예매 정보 관리, 가격 계산
Screening: 상영 정보 관리
DiscountPolicy: 할인 계산
// 3. 협력 설계
Screening → Movie → DiscountPolicy
// 4. 타입과 클래스
public class Movie {
private DiscountPolicy discountPolicy;
public Money calculateMovieFee(Screening screening) {
return fee.minus(discountPolicy.calculateDiscount(screening));
}
}┌─────────────────────────────────────────────────────┐
│ │
│ 동일한 행동을 제공하는 정적 모델이 여러 개 있다면 │
│ 현재의 설계에서 요구되는 변경을 │
│ 부드럽게 수용할 수 있는 설계를 선택하라 │
│ │
└─────────────────────────────────────────────────────┘
예시: 할인 정책 설계
// 방법 1: 상속 기반
abstract class DiscountPolicy {
abstract Money calculateDiscount(Screening screening);
}
// 방법 2: 인터페이스 기반
interface DiscountPolicy {
Money calculateDiscount(Screening screening);
}
// 방법 3: 전략 패턴
class Movie {
private DiscountStrategy strategy;
}
// 선택 기준:
// - 어떤 변경이 예상되는가?
// - 새로운 할인 정책 추가?
// - 할인 정책 동적 변경?
// - 할인 정책 조합?
→ 예상되는 변경에 가장 유연한 방법 선택!1. 행동 정의
"할인 정책 적용"
2. 예상 변경 파악
- 새로운 정책 추가 (높음)
- 정책 동적 변경 (중간)
- 정책 조합 (낮음)
3. 정적 모델 옵션 생성
- 인터페이스
- 추상 클래스
- 전략 패턴
- 데코레이터 패턴
4. 변경 시나리오 시뮬레이션
각 옵션에서 변경이 얼마나 쉬운가?
5. 최적 모델 선택
가장 유연한 옵션 선택
6. 구현 및 검증
실제 변경으로 검증
도메인 (Domain):
사용자가 프로그램을 사용하는 대상 영역
모델 (Model):
지식을 선택적으로 단순화하고
의식적으로 구조화한 형태
도메인 모델 (Domain Model):
대상 영역에 대한 지식을
선택적으로 단순화하고
의식적으로 구조화한 형태
예시:
도메인: 영화 예매 시스템
도메인 모델:
┌──────────┐ ┌──────────┐
│ 영화 │──────│ 상영 │
└──────────┘ └──────────┘
│
│ 적용
▼
┌──────────┐
│ 할인정책 │
└──────────┘
△
│
┌────┴────┐
│ │
금액할인 비율할인
┌─────────────────────────────────────────────────────┐
│ │
│ 도메인 모델은: │
│ │
│ ✅ 시작점 │
│ ✅ 힌트 제공 │
│ ✅ 개념의 이름과 의미 │
│ ✅ 관계에 대한 가이드 │
│ │
│ ❌ 최종 목표가 아님 │
│ ❌ 코드 구조와 일치 필수 아님 │
│ ❌ 변경 불가능한 청사진 아님 │
│ │
└─────────────────────────────────────────────────────┘
중요한 것:
1순위: 소프트웨어의 기능
2순위: 객체의 책임
3순위: 객체의 협력
도메인 모델은 이를 돕는 도구일 뿐!
❌ 잘못된 접근:
도메인 모델:
영화 - 감독 - 배우 - 장르 - 상영관
코드:
class Movie {
private Director director;
private List<Actor> actors;
private Genre genre;
private Theater theater;
}
문제:
- 협력 없음
- 책임 불명확
- 단순 데이터 저장소
올바른 접근:
✅ 협력 중심 설계:
필요한 협력:
"영화 가격 계산"
"할인 적용"
"상영 시간 확인"
코드:
class Movie {
private Money fee;
private DiscountPolicy discountPolicy;
public Money calculateMovieFee(Screening screening) {
return fee.minus(
discountPolicy.calculateDiscount(screening)
);
}
}
→ 도메인 모델과 다르지만 협력에 적합!
초기 도메인 모델:
영화 → 할인정책
협력 설계 후:
영화 → 할인정책
│
└─→ 할인조건
코드 구현 후:
영화 → 할인정책 → 할인조건
│
└─→ 상영
최종 도메인 모델:
영화, 할인정책, 할인조건, 상영의 협력 관계
→ 코드에 맞춰 도메인 모델 진화!
┌─────────────────────────────────────────────────────┐
│ │
│ 도메인 모델 ≠ 클래스 다이어그램 │
│ │
│ 도메인의 핵심을 표현할 수 있는 모든 것: │
│ │
│ - 클래스 다이어그램 │
│ - 순서도 │
│ - 상태 다이어그램 │
│ - 협력 다이어그램 │
│ - 사용자 스토리 │
│ - 시나리오 │
│ - 심지어 그림이나 글! │
│ │
└─────────────────────────────────────────────────────┘
게임에 다양한 몬스터가 등장한다:
- 용 (Dragon): 체력 230, 공격 "불을 내뿜는다"
- 트롤 (Troll): 체력 48, 공격 "곤봉으로 때린다"
새로운 몬스터를 계속 추가해야 한다.
// Step 01: 클래스 상속
public abstract class Monster {
private int health;
public Monster(int health) {
this.health = health;
}
abstract public String getAttack();
}
public class Dragon extends Monster {
public Dragon() {
super(230);
}
@Override
public String getAttack() {
return "용은 불을 내뿜는다";
}
}
public class Troll extends Monster {
public Troll() {
super(48);
}
@Override
public String getAttack() {
return "트롤은 곤봉으로 때린다";
}
}문제점:
새로운 몬스터 추가마다:
1. 새로운 클래스 작성
2. 코드 컴파일
3. 배포
→ 클래스 폭발!
→ 유연성 부족!
→ 런타임 변경 불가!
┌─────────────────────────────────────────────────────┐
│ │
│ TYPE OBJECT 패턴: │
│ │
│ 어떤 인스턴스가 │
│ 다른 인스턴스의 타입을 표현하는 방법 │
│ │
│ 타입을 클래스가 아닌 객체로 표현! │
│ │
└─────────────────────────────────────────────────────┘
// Step 02: 타입을 객체로!
// 타입을 표현하는 클래스
public class Breed {
private String name;
private int health;
private String attack;
public Breed(String name, int health, String attack) {
this.name = name;
this.health = health;
this.attack = attack;
}
public int getHealth() {
return health;
}
public String getAttack() {
return attack;
}
}
// 몬스터 클래스
public class Monster {
private int health;
private Breed breed; // 타입을 객체로!
public Monster(Breed breed) {
this.breed = breed;
this.health = breed.getHealth();
}
public String getAttack() {
return breed.getAttack();
}
}사용:
// 타입 객체 생성
Breed dragonBreed = new Breed("용", 230, "용은 불을 내뿜는다");
Breed trollBreed = new Breed("트롤", 48, "트롤은 곤봉으로 때린다");
// 몬스터 생성
Monster dragon = new Monster(dragonBreed);
Monster troll = new Monster(trollBreed);
System.out.println(dragon.getAttack()); // "용은 불을 내뿜는다"
System.out.println(troll.getAttack()); // "트롤은 곤봉으로 때린다"{
"breeds": [
{
"name": "용",
"health": 230,
"attack": "용은 불을 내뿜는다"
},
{
"name": "트롤",
"health": 48,
"attack": "트롤은 곤봉으로 때린다"
},
{
"name": "오크",
"health": 65,
"attack": "오크는 도끼로 내리친다"
}
]
}로딩 코드:
public class BreedRepository {
private Map<String, Breed> breeds = new HashMap<>();
public void loadFromJson(String jsonFile) {
// JSON 파싱
JsonArray breedArray = parseJson(jsonFile);
for (JsonObject obj : breedArray) {
Breed breed = new Breed(
obj.getString("name"),
obj.getInt("health"),
obj.getString("attack")
);
breeds.put(breed.getName(), breed);
}
}
public Breed getBreed(String name) {
return breeds.get(name);
}
}
// 사용
BreedRepository repo = new BreedRepository();
repo.loadFromJson("monster.json");
Monster dragon = new Monster(repo.getBreed("용"));
Monster orc = new Monster(repo.getBreed("오크"));✅ 장점:
1. 새로운 타입 추가 용이
- 코드 수정 없이 데이터만 추가
- 컴파일 불필요
- 런타임 로딩 가능
2. 유연성
- 타입을 동적으로 변경 가능
- 타입 조합 가능
3. 설정 외부화
- JSON, XML, DB 등으로 관리
- 비개발자도 수정 가능
4. 메모리 효율
- 동일한 타입은 한 번만 생성
- 여러 인스턴스가 공유
Monster (추상)
△
│
┌───────┼───────┐
│ │ │
Dragon Troll Orc ...
- 각 타입마다 클래스 필요
- 컴파일 타임 고정
- 코드 변경 필요
Monster ─────> Breed
│ ↑
│ │
└─ breed ──────┘
Breed instances:
- dragonBreed
- trollBreed
- orcBreed
...
- 타입 = 데이터
- 런타임 생성 가능
- 설정으로 관리
분석 모델 (Analysis Model):
- 문제 도메인에 초점
- "무엇을" 해야 하는가
- 요구사항 이해
↓
설계 모델 (Design Model):
- 솔루션 도메인에 초점
- "어떻게" 구현할 것인가
- 기술적 관점
↓
구현 모델 (Implementation Model):
- 프로그래밍 언어로 표현
- 실제 동작하는 코드
분석 모델:
┌──────────┐ ┌──────────┐
│ 고객 │──────│ 주문 │
└──────────┘ └──────────┘
설계 모델:
┌──────────┐ ┌──────────┐ ┌──────────┐
│Customer │──────│ Order │──────│OrderItem │
└──────────┘ └──────────┘ └──────────┘
구현 모델:
class Customer {
private List<Order> orders;
}
class Order {
private List<Item> items;
}
문제:
- 모델 간 불일치
- 개념 불명확
- 유지보수 어려움
┌─────────────────────────────────────────────────────┐
│ │
│ 분석, 설계, 구현은 │
│ 하나의 모델로 통합되어야 한다 │
│ │
│ 전체 개발 주기 동안 │
│ 동일한 모델을 유지하라 │
│ │
└─────────────────────────────────────────────────────┘
통합 모델:
개념:
영화 예매 시스템에서
영화는 할인 정책을 가진다
분석:
"영화에 할인 정책을 적용한다"
설계:
Movie ─────> DiscountPolicy
구현:
public class Movie {
private DiscountPolicy discountPolicy;
public Money calculateFee(Screening screening) {
return fee.minus(
discountPolicy.calculateDiscount(screening)
);
}
}
→ 모든 단계에서 동일한 개념과 구조!
세 모델 모두:
1. 행동에 영향 받음
- 어떤 기능을 제공하는가
- 어떤 책임을 가지는가
2. 변경에 영향 받음
- 어떤 변경이 예상되는가
- 어떻게 대응할 것인가
→ 행동과 변경이 모델의 중심!
예시:
행동: "할인 정책 적용"
변경: "새로운 할인 정책 추가"
분석:
영화는 다양한 할인 정책을 적용할 수 있다
설계:
Movie는 DiscountPolicy 인터페이스에 의존
다양한 구현체로 확장 가능
구현:
interface DiscountPolicy { ... }
class AmountDiscountPolicy implements DiscountPolicy { ... }
class PercentDiscountPolicy implements DiscountPolicy { ... }
→ 모든 단계에서 동일한 구조!
┌─────────────────────────────────────────────────────┐
│ │
│ 객체지향 패러다임의 가장 큰 장점: │
│ │
│ 전체 개발 주기 동안 │
│ 동일한 설계 기법과 모델링 방법을 사용할 수 있다 │
│ │
│ 분석 → 설계 → 구현 │
│ 모두 객체와 협력으로 표현! │
│ │
└─────────────────────────────────────────────────────┘
1. 도메인 이해부터 시작
- 핵심 개념 파악
- 용어 정리
2. 협력 시나리오 작성
- 객체들이 어떻게 협력하는가
- 메시지 흐름은?
3. 책임 할당
- 누가 무엇을 하는가
- 정보 전문가는?
4. 타입 정의
- 협력에 필요한 타입은?
- 인터페이스는?
5. 클래스 구현
- 협력을 코드로 구현
- 변경 고려
6. 모델 진화
- 코드에 맞춰 도메인 모델 업데이트
- 피드백 반영
→ 반복하며 모델 정제!
| 모델 | 본질 | 구성 요소 | 역할 |
|---|---|---|---|
| 동적 모델 | 실행 구조 | 객체, 협력 | 행동 정의 |
| 정적 모델 | 코드 구조 | 타입, 관계 | 구현 지원 |
1. 동적 모델이 정적 모델을 주도
→ 행동이 타입을 결정
2. 행동이 코드를 결정
→ 협력 먼저, 타입은 나중
3. 변경을 고려하라
→ 유연한 설계 선택
4. 도메인 모델은 출발점
→ 협력에 맞춰 진화
5. 분석/설계/구현 통합
→ 하나의 모델 유지
┌─────────────────────────────────────────────────────┐
│ │
│ 1. 행동 파악 (What) │
│ "무엇을 해야 하는가?" │
│ │
│ 2. 협력 설계 (How - 동적) │
│ "객체들이 어떻게 협력하는가?" │
│ │
│ 3. 변경 고려 (When) │
│ "어떤 변경이 예상되는가?" │
│ │
│ 4. 타입 정의 (What - 정적) │
│ "어떤 타입이 필요한가?" │
│ │
│ 5. 클래스 구현 (How - 정적) │
│ "어떻게 구현할 것인가?" │
│ │
│ 6. 검증 및 진화 │
│ "협력이 잘 동작하는가?" │
│ "모델을 개선할 수 있는가?" │
│ │
└─────────────────────────────────────────────────────┘
1. 행동부터 생각하라
- "무엇을 해야 하는가?"
- 객체의 책임은?
2. 협력을 설계하라
- 객체들이 어떻게 협력?
- 메시지 흐름은?
3. 변경을 고려하라
- 어떤 변경이 예상?
- 유연한 설계는?
4. 도메인 모델을 진화시켜라
- 코드에 맞춰 업데이트
- 협력 반영
5. TYPE OBJECT 패턴 활용
- 타입이 많고 자주 변경되면
- 데이터로 타입 표현
6. 모델을 통합하라
- 분석/설계/구현 일치
- 하나의 언어 사용
1. 타입부터 설계하지 말라
- 데이터 중심 설계 위험
- 협력 부재
2. 도메인 모델에 집착하지 말라
- 코드가 우선
- 협력에 맞춰 변경
3. 모델을 분리하지 말라
- 분석 ≠ 설계 ≠ 구현
- 통합된 모델 유지
4. 정적 모델에 갇히지 말라
- 협력이 불편하면 변경
- 타입은 도구일 뿐
5. 클래스부터 만들지 말라
- 협력 먼저
- 클래스는 나중
6. 변경을 무시하지 말라
- 유연성 고려
- 확장 가능한 설계
1. 동적 모델 → 정적 모델
→ 행동이 타입을 결정
2. 협력이 설계의 중심
→ 객체의 행동에 집중
3. 도메인 모델은 가이드
→ 협력에 맞춰 진화
4. TYPE OBJECT 패턴
→ 타입을 데이터로 표현
5. 분석/설계/구현 통합
→ 하나의 모델로 일관성
6. 변경 주도 설계
→ 유연성이 핵심
7. 객체지향의 본질
→ 객체와 협력
8. 코드는 살아있다
→ 지속적으로 진화
┌─────────────────────────────────────────────────────┐
│ │
│ "정적 모델은 동적 모델의 그림자다" │
│ │
│ 객체의 협력(동적 모델)이 │
│ 코드 구조(정적 모델)를 결정한다 │
│ │
│ 도메인 모델은 출발점일 뿐 │
│ 최종 목적지는 동작하는 협력이다 │
│ │
│ 분석, 설계, 구현을 구분하지 말고 │
│ 하나의 통합된 모델로 생각하라 │
│ │
│ 행동과 변경에 집중하라 │
│ 그러면 좋은 설계가 자연스럽게 나온다 │
│ │
└─────────────────────────────────────────────────────┘
프로젝트 시작할 때:
1. 도메인 이해
- 핵심 개념 파악
- 전문가와 대화
2. 협력 시나리오 작성
- 사용자 스토리
- 유스케이스
3. 객체 식별
- 책임 할당
- 협력 설계
4. 프로토타입 작성
- 핵심 협력 구현
- 검증
5. 반복 개선
- 피드백 수렴
- 모델 진화
→ 전체 과정에서 동일한 모델 유지!
- Appendix B: 타입 계층 구현 → 정적 모델
- 전체 책: 역할, 책임, 협력 → 동적 모델
- Chapter 2: 객체지향 프로그래밍 → 협력 설계
- 다음 단계:
- 도메인 주도 설계 (DDD)
- 이벤트 스토밍
- 행동 주도 개발 (BDD)
- 테스트 주도 개발 (TDD)
Chapter 1-15 + Appendix A-C:
객체지향의 본질 = 협력
협력을 설계하라 (동적 모델)
↓
타입을 정의하라 (정적 모델)
↓
코드로 구현하라
↓
검증하고 개선하라
↓
반복하라
→ 이것이 객체지향!