콘텐츠로 이동

오브젝트 실전 강의 교재

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 하향식 기능 분해의 문제점

  1. 변경에 취약 — 데이터 구조 변경이 모든 함수에 영향.
  2. 재사용 어려움 — 함수가 전역 데이터에 결합.
  3. 테스트 어려움 — 전역 상태 의존.
  4. 새 기능 추가가 산탄총 수술 — 여러 함수 동시 수정.

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.


핵심 교훈

  1. 프로시저 추상화 → 데이터 추상화 → ADT → 클래스 — 추상화 도구의 역사적 진화.
  2. 하향식 기능 분해는 작은 알고리즘에만 — 시스템 설계엔 부족.
  3. 모듈 의 한계 (인스턴스 1개) 를 ADT 가 극복.
  4. 클래스 = ADT + 상속·다형성 — 단순 데이터 묶음 이상.
  5. 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 에 불필요하게 클래스를 만들고 있지 않은가

퀴즈

  1. 모듈 의 한계 한 가지와 ADT 가 해결한 방식?
  2. 하향식 기능 분해 가 시스템 설계에 부족한 이유 두 가지?
  3. 클래스 = ADT + 무엇 인가?
  4. OO 의 본질 이 클래스가 아니라 무엇인가?
  5. JPA Entity 가 ADT 처럼 쓰이는 게 문제인 이유?

정답·해설

  1. 모듈은 인스턴스 1개 (한 모듈 = 한 데이터 묶음). ADT 가 인스턴스 여러 개 가능 (예: 여러 Stack 인스턴스). 그러나 ADT 도 상속·다형성은 없음 — 클래스가 그 다음.
  2. (1) 데이터 변경이 모든 함수 영향 — 결합도 폭증. (2) 새 기능 추가가 산탄총 수술 — 여러 함수 동시 수정. 작은 알고리즘 (정렬 등) 에는 OK, 큰 시스템에는 부족.
  3. 상속 + 다형성. 한 인터페이스 = 여러 구현. ADT 는 한 데이터 타입 = 한 구현, 클래스는 다형성으로 확장.
  4. 객체 협력. 작은 객체들이 메시지로 요청·응답하며 시스템 행동을 만드는 것. 단순히 클래스로 묶기만 = OO 아님 (절차지향의 OO 코스튬).
  5. 다형성·상속의 가치 잃음. JPA Entity 가 단순 자료 구조 + getter/setter + Service 가 모든 결정 = 절차지향. 도메인 행동을 Entity 에 두고 다형성 (Strategy·Template Method) 활용해야 OO 가치 발휘.

다음 장 예고 — 8장: 의존성 관리하기

객체 협력이 곧 의존성 — 의존성이 변경의 길. 컴파일타임·런타임 의존성, 컨텍스트 독립성, new 의 함정, 명시적 의존성. Effective Java Item 5 (DI)·64 (인터페이스 참조) 와 직접 연결.