오브젝트 실전 강의 교재¶
7장 — 객체 분해¶
원서: 조영호 『오브젝트』 대상: Java/Spring 백엔드 입문~중급 수강생 형식: 개념 → 비유 → 예시 → 핵심 교훈 → 현업 예제 → 함정 → 체크리스트 → 퀴즈(정답 분리)
0. 이 장을 시작하기 전에¶
0.1 학습 목표¶
- 프로시저 추상화 (기능 분해) vs 데이터 추상화 (객체) 의 역사적 차이.
- 하향식 기능 분해 의 한계.
- 추상 데이터 타입 (ADT) vs 클래스 의 차이.
- "클래스는 ADT 의 일종이지만 더 많은 것" 의 의미.
0.2 큰 그림 — 추상화의 두 흐름¶
[ 프로시저 추상화 ] [ 데이터 추상화 ]
메인 함수가 시스템 데이터 자체가 객체
기능 분해 (top-down) 추상 데이터 타입 (ADT) → 클래스
절차지향·구조적 프로그래밍 객체지향
1970~80s 주류 1990s 이후 주류
비유 — "공장 라인 vs 자율 작업장"
컨베이어 벨트 공장에서는 제품이 한 라인을 따라 흐르고, 각 단계가 정해진 순서대로 자기 차례의 가공을 한 다음 다음 단계로 넘깁니다. 반면 자율 작업장에서는 작업장마다 자기가 맡은 일을 스스로 끝내고, 필요할 때 옆 작업장에 일을 부탁합니다.
두 추상화도 같은 대비를 이룹니다. 프로시저 추상화는 컨베이어 벨트처럼 메인 함수가 단계들을 정해진 순서로 호출하고, 데이터 추상화는 자율 작업장처럼 각 객체가 자기 데이터와 행동을 스스로 책임집니다.
0.3 현업에서 왜 중요한가¶
- 절차지향 사고가 OO 와 어떻게 다른지 명확히 — 4·5장의 비교를 역사적 관점에서.
- 추상 데이터 타입을 이해해야 record·sealed·enum 활용이 정확.
1. 프로시저 추상화와 데이터 추상화¶
1.1 두 추상화의 정의¶
- 프로시저 추상화: 어떤 일을 어떻게 하는지를 추상화. 단계·절차 중심.
- 데이터 추상화: 어떤 것이 무엇인지 추상화. 데이터 + 그 데이터의 행동.
1.2 추상화의 도구 진화¶
| 시대 | 도구 |
|---|---|
| 절차지향 | 함수·서브루틴·모듈 |
| 데이터 추상화 초기 | 추상 데이터 타입 (ADT) |
| 객체지향 | 클래스·인터페이스·다형성 |
| 함수형 | 고차 함수·대수적 데이터 타입 |
2. 프로시저 추상화와 기능 분해¶
2.1 메인 함수로서의 시스템¶
main()
├── parseInput()
├── validate()
├── compute()
│ ├── step1()
│ ├── step2()
│ └── step3()
└── printResult()
→ 시스템 = 메인 + 그 아래 함수 트리. 위에서 아래로 분해 (top-down).
2.2 급여 관리 시스템 (책 예제 요약)¶
// 절차지향 — 함수 + 전역 데이터
struct Employee { char name[50]; int salary; ... };
Employee employees[100];
void calculatePay(int id) { ... }
void printReport() { ... }
void deductTax(int id) { ... }
데이터 (Employee 배열) 는 함수들에 공유되는 전역 상태. 모든 함수가 그 구조 알아야.
2.3 하향식 기능 분해의 문제점¶
- 변경에 취약 — 데이터 구조 변경이 모든 함수에 영향.
- 재사용 어려움 — 함수가 전역 데이터에 결합.
- 테스트 어려움 — 전역 상태 의존.
- 새 기능 추가가 산탄총 수술 — 여러 함수 동시 수정.
2.4 언제 하향식 분해가 유용한가¶
- 단일 기능 알고리즘 (예: 정렬·트리 순회).
- 명확한 단계가 있는 워크플로 (예: 빌드 스크립트).
→ 시스템 설계 전체 에는 부족. 작은 알고리즘에 한정.
3. 모듈¶
3.1 정보 은닉과 모듈¶
모듈 = 관련된 데이터 + 함수 를 한 단위로 묶고 내부 디테일 숨김.
→ 객체지향의 선조. 1970s Parnas 의 정보 은닉 원칙.
3.2 모듈의 장점과 한계¶
장점: - 정보 은닉으로 변경 영향 좁힘. - 이름공간 분리.
한계: - 인스턴스가 하나 — 같은 모듈 (예: Employee 모듈) 의 여러 인스턴스 못 만듦. - 상속·다형성 없음.
4. 데이터 추상화와 추상 데이터 타입¶
4.1 추상 데이터 타입 (ADT)¶
데이터 + 그 데이터에 작용하는 연산 을 한 단위로 정의. 내부 표현 숨김.
예: Stack ADT
- 연산: push(x), pop(), peek(), isEmpty().
- 내부 표현: 배열? 연결 리스트? — 외부가 모름.
// C 의 ADT 흉내 — 모듈 + 인스턴스 가능
typedef struct Stack* StackHandle;
StackHandle stack_create();
void stack_push(StackHandle s, int x);
int stack_pop(StackHandle s);
void stack_destroy(StackHandle s);
→ 모듈의 한계 (인스턴스 1개) 극복. 그러나 상속·다형성 없음.
5. 클래스¶
5.1 클래스는 추상 데이터 타입인가?¶
부분적으로 그렇다: - 데이터 + 연산 묶음 (ADT 의 성질). - 인스턴스 여럿 (ADT 의 진화). - 상속·다형성 (ADT 에 없는 추가).
5.2 추상 데이터 타입에서 클래스로 변경하기¶
- ADT: 한 데이터 타입 = 한 구현.
- 클래스: 한 인터페이스 = 여러 구현 (다형성).
// 클래스 (인터페이스 + 다형성)
public interface Stack<E> {
void push(E e);
E pop();
}
public class ArrayStack<E> implements Stack<E> { ... }
public class LinkedStack<E> implements Stack<E> { ... }
→ 호출자는 Stack<E> 만 알고, 구현 교체 자유.
5.3 변경을 기준으로 선택하라¶
- 데이터 표현 변경이 잦음 → ADT (단일 구현 캡슐화로 충분).
- 새 구현·새 행동 추가가 잦음 → 클래스 (다형성).
- 둘 다 잦음 → 클래스 + 인터페이스.
5.4 협력이 중요하다¶
OO 의 진짜 가치는 ADT 가 아니라 객체 협력: - 한 객체가 다른 객체에게 요청 (메시지). - 다형성으로 응답 방식 다양. - 작은 객체들의 협력이 시스템 행동.
→ 단순 ADT 묶음은 OO 가 아님. 협력하는 자율 객체 가 OO.
핵심 교훈¶
- 프로시저 추상화 → 데이터 추상화 → ADT → 클래스 — 추상화 도구의 역사적 진화.
- 하향식 기능 분해는 작은 알고리즘에만 — 시스템 설계엔 부족.
- 모듈 의 한계 (인스턴스 1개) 를 ADT 가 극복.
- 클래스 = ADT + 상속·다형성 — 단순 데이터 묶음 이상.
- OO 의 본질은 클래스가 아니라 객체 협력 — 단순 ADT 모음은 OO 아님.
현업 예제 — 절차지향 vs OO 의 분기점¶
절차지향 코드 (4장의 ReservationAgency)¶
public class ReservationAgency {
public Reservation reserve(...) {
// 한 함수가 모든 단계 (parse → validate → calculate → save)
// 도메인 객체 (Movie·Screening) 는 자료 구조
}
}
→ 메인 함수로서의 시스템.
OO 코드 (5장 책임 주도 설계)¶
public class Screening {
public Reservation reserve(Customer c, int audience) {
Money fee = movie.calculateMovieFee(this);
return new Reservation(c, this, fee, audience);
}
}
public class Movie {
public Money calculateMovieFee(Screening s) {
return fee.minus(discountPolicy.calculateDiscountAmount(s));
}
}
public class DiscountPolicy {
// 다형성으로 정책 분기
}
→ 객체 협력. 각 객체가 자기 일을 알고, 다른 객체에게 요청만.
함정 / 주의¶
- 클래스만 만들면 OO 가 아님 — 자료 구조 + getter/setter + 외부 Service = 절차지향의 OO 코스튬.
- 하향식 기능 분해를 시스템 전체에 적용 = 모놀리식·거대 함수 (악취).
- ADT 와 클래스 혼동 — JPA Entity 가 ADT 처럼 쓰이면 다형성 가치 잃음.
- record 는 불변 ADT 의 정확한 구현. 다형성 필요 없으면 record 충분.
체크리스트¶
- 도메인 객체가 자료 구조 + Service 의존 구조인가 (절차지향 신호)
- 한 함수에 모든 단계 (parse·validate·process·save) 가 섞여 있는가
- 다형성을 진짜 활용하는가 (단일 구현 인터페이스 X)
- record 로 충분한 ADT 에 불필요하게 클래스를 만들고 있지 않은가
퀴즈¶
- 모듈 의 한계 한 가지와 ADT 가 해결한 방식?
- 하향식 기능 분해 가 시스템 설계에 부족한 이유 두 가지?
- 클래스 = ADT + 무엇 인가?
- OO 의 본질 이 클래스가 아니라 무엇인가?
- JPA Entity 가 ADT 처럼 쓰이는 게 문제인 이유?
정답·해설¶
- 모듈은 인스턴스 1개 (한 모듈 = 한 데이터 묶음). ADT 가 인스턴스 여러 개 가능 (예: 여러 Stack 인스턴스). 그러나 ADT 도 상속·다형성은 없음 — 클래스가 그 다음.
- (1) 데이터 변경이 모든 함수 영향 — 결합도 폭증. (2) 새 기능 추가가 산탄총 수술 — 여러 함수 동시 수정. 작은 알고리즘 (정렬 등) 에는 OK, 큰 시스템에는 부족.
- 상속 + 다형성. 한 인터페이스 = 여러 구현. ADT 는 한 데이터 타입 = 한 구현, 클래스는 다형성으로 확장.
- 객체 협력. 작은 객체들이 메시지로 요청·응답하며 시스템 행동을 만드는 것. 단순히 클래스로 묶기만 = OO 아님 (절차지향의 OO 코스튬).
- 다형성·상속의 가치 잃음. JPA Entity 가 단순 자료 구조 + getter/setter + Service 가 모든 결정 = 절차지향. 도메인 행동을 Entity 에 두고 다형성 (Strategy·Template Method) 활용해야 OO 가치 발휘.
다음 장 예고 — 8장: 의존성 관리하기¶
객체 협력이 곧 의존성 — 의존성이 변경의 길. 컴파일타임·런타임 의존성, 컨텍스트 독립성, new 의 함정, 명시적 의존성. Effective Java Item 5 (DI)·64 (인터페이스 참조) 와 직접 연결.