Skip to content

object-nextstep21/object

 
 

Repository files navigation

스터디 참가자

@mo-bile / @chanani / @Eundms

🚀 오브젝트 스터디 커리큘럼


전체 로드맵 요약

완료 날짜 단계 주차 범위(장) 핵심 주제 실습/산출물 면접 포인트
[V] 2026-01-29 1단계: 문맥 익히기 2주차 2, 3장 객체 협력, 역할/책임, RDD Movie–Screening 협력 코드, 메시지 다이어그램 다형성으로 if 제거, 컴파일/런타임 의존성
[V] 2026-02-05 2단계: 리팩터링 🔥 3주차 4장 데이터 중심 설계의 함정 ReservationAgency 문제 코드 분석 Getter/Setter 지양, 캡슐화
[V] 2026-02-12 2단계: 리팩터링 🔥 4주차 5장 GRASP 책임 할당 다형성/변경 보호 적용 리팩터링 책임 할당 기준
[V] 2026-02-25 3단계: 의존성 관리 5주차 6, 8장 좋은 인터페이스, 의존성 주입 디미터/묻지말고시켜라, DI 적용 CQS, 결합도
[V] 2026-03-05 3단계: SOLID 6주차 9장 OCP/DIP/Factory new 제거 리팩터링 생성·사용 분리
[V] 2026-03-12 4단계: 상속의 한계 7주차 10장 취약한 기반 클래스 Phone 상속 문제 재현 상속의 단점
[V] 2026-03-19 4단계: 합성 8주차 11장 합성으로 유연성 요금 정책 합성 상속 vs 합성
[ ] 2026-03-26 5단계: LSP 9주차 13장 서브타이핑 직사각형/정사각형 분석 LSP 기준
[ ] 5단계: 패턴 10주차 14, 15장 일관성/패턴 조건·규칙 분리 패턴의 본질

|


주차별 상세 계획

완료 날짜 주차 학습 포인트 반드시 할 것
[V] 2026-01-29 2주차 책임 먼저 생각하기(RDD) 협력 다이어그램 직접 작성
[V] 2026-02-05 3주차 데이터 중심 설계의 문제 체감 변경 시 파급효과 기록
[V] 2026-02-12 4주차 GRASP로 책임 재배치 다형성/변경 보호 적용
[V] 2026-02-25 5주차 인터페이스 설계 원칙 디미터 위반 코드 제거
[V] 2026-03-05 6주차 SOLID 실전 적용 DI/Factory로 리팩터링
[V] 2026-03-12 7주차 상속의 함정 경험 중복/결합도 포인트 정리
[V] 2026-03-19 8주차 합성으로 문제 해결 런타임 조합 코드 작성
[ ] 2026-03-26 9주차 올바른 상속 기준 LSP 위반 사례 설명 연습
[ ] 10주차 일관성 있는 협력 패턴으로 정리

"설계는 코드를 배치하는 것입니다." — 직접 리팩터링하며 체득하세요.

주차별 계획

2주차 (2026-01-29) 9주 완성 플랜의 **2주차(2장~3장)**는 객체지향의 문법(Syntax)이 아닌 **'문맥(Context)'**을 익히는 단계입니다. 클래스를 먼저 만드는 습관을 버리고, **'책임'을 중심으로 객체 간의 협력**을 그리는 사고방식으로 전환하는 것이 핵심 목표입니다.

스터디원들과 함께 **'실무 적용'**과 '원리 이해' 두 마리 토끼를 잡을 수 있는 학습 방향과 실습 과제를 제안합니다.


🎯 2주차 스터디 학습 방향: "객체지향의 문해력 키우기"

학습 범위: 2장 (객체지향 프로그래밍) ~ 3장 (역할, 책임, 협력)

1. 코드 너머의 '의존성' 파악하기 (2장)

  • 단순히 코드를 따라 치는 것을 넘어, 상속과 다형성이 어떻게 if-else 조건문을 제거하는지 확인해야 합니다.
  • 컴파일 타임 의존성런타임 의존성이 다르다는 사실이 유연한 설계(OCP)의 핵심임을 이해해야 합니다. 코드는 추상 클래스(DiscountPolicy)에 의존하지만, 실행 시점에는 구체 클래스(AmountDiscountPolicy 등)와 협력한다는 점을 명확히 파악하세요.

2. 구현보다 '책임' 먼저 생각하기 (3장)

  • 이 책의 핵심 철학인 **'책임 주도 설계(RDD)'**의 기초를 다집니다. "이 객체에 어떤 데이터(필드)가 필요한가?"를 먼저 고민하는 습관을 버려야 합니다.
  • 대신 **"이 객체는 무엇을 해야 하는가?(책임)"**를 먼저 결정하고, 그 책임을 수행하기 위해 누구와 협력해야 하는지를 고민하는 사고방식을 훈련합니다.

💻 스터디 실습 과제 (Action Item)

스터디 모임 전에 각자 수행하고, 모임 시간에는 결과물을 비교하며 토론하는 방식을 추천합니다.

✅ [과제 1] 영화 예매 시스템 코드 필사 및 변형 (Hands-on)

  • 내용: 2장에 나오는 Movie, Screening, DiscountPolicy(추상 클래스), AmountDiscountPolicy, PercentDiscountPolicy 코드를 직접 IDE에 타이핑하여 구현합니다.
  • 핵심 미션:
    1. Movie 클래스 코드 내에 if문이나 switch문 없이 할인 정책이 적용되는 과정을 디버거(Debugger)로 추적해 봅니다.
    2. 변형 실습: NoneDiscountPolicy(할인이 없는 정책) 클래스를 직접 추가해 봅니다. 이때 Movie 클래스의 코드를 단 한 줄도 수정하지 않고 기능이 확장되는지 확인하세요. 이것이 **개방-폐쇄 원칙(OCP)**을 체감하는 가장 좋은 방법입니다.

✅ [과제 2] 의존성 다이어그램 그리기 (Visualizing)

  • 내용: 작성한 코드의 **클래스 다이어그램(정적 구조)**과 실제 코드가 실행될 때 객체들이 연결되는 **객체 다이어그램(동적 구조)**을 각각 그려봅니다.
  • 핵심 미션:
    • MovieDiscountPolicy를 바라보는 화살표(컴파일 타임)와, 실제 실행 시 Movie 인스턴스가 AmountDiscountPolicy 인스턴스를 바라보는 화살표(런타임)가 어떻게 다른지 그림으로 그려서 스터디원들에게 설명해 봅니다.

✅ [과제 3] CRC 카드 놀이 (Thinking) - 3장 관련

  • 내용: 코딩 전에 인덱스 카드(혹은 포스트잇)를 사용하여 **역할(Role), 책임(Responsibility), 협력(Collaboration)**을 설계하는 연습을 합니다.
  • 상황: "영화 예매 시스템에 '환불' 기능을 추가한다면?"
  • 핵심 미션:
    • 구현(메서드, 변수)을 생각하지 말고, **'환불하라'**는 메시지를 누가 받아야 할지, 그 객체는 누구에게 협력을 요청해야 할지를 카드에 적어보며 설계를 구상합니다.

🗣️ 스터디 토론 주제 (Interview Prep)

면접에서 자주 나오는 질문들에 대해 책의 내용을 바탕으로 답변을 정리해 봅니다.

  1. "상속은 언제 사용해야 하나요?"

    • 가이드: 단순히 코드를 재사용하기 위해서가 아니라, 부모 클래스와 자식 클래스가 **'타입 계층(Type Hierarchy)'**을 이룰 때, 즉 부모가 하는 일을 자식이 대체할 수 있을 때 사용해야 한다는 점을 2장의 다형성 개념과 연결해 이야기해 봅니다.
  2. "컴파일 타임 의존성과 런타임 의존성이 다르면 왜 좋은가요?"

    • 가이드: 코드를 수정하지 않고도 객체를 갈아끼움으로써 기능을 변경/확장할 수 있는 유연성(Flexibility) 측면과, 반대로 코드를 읽을 때 흐름을 파악하기 어려워지는 **복잡성(Complexity)**의 트레이드오프에 대해 토론합니다.
  3. "데이터 주도 설계 vs 책임 주도 설계의 차이는?"

    • 가이드: Getter/Setter가 많은 객체(수동적 데이터 저장소)와 스스로 상태를 처리하는 객체(자율적 존재)의 차이를 3장의 내용을 바탕으로 설명할 수 있어야 합니다.

💡 2주차 스터디 팁: 이번 주차는 **"객체지향은 클래스를 만드는 것이 아니라, 협력하는 객체들을 만드는 것"**이라는 패러다임의 전환을 받아들이는 것이 가장 중요합니다. 코드를 짤 때 class Movie를 먼저 떠올리기보다 Movie라는 객체가 어떤 메시지를 받고 누구에게 메시지를 보낼지를 먼저 상상해 보세요.

3주차 (2026-02-05) **3주차(4장)**는 이 책에서 가장 중요한 전환점 중 하나인 **'설계 품질과 트레이드오프'**를 다루는 주간입니다.

2주차(2-3장)에서 객체지향의 이상적인 모습을 배웠다면, 3주차인 이번 주는 **"데이터 중심 설계(Data-Centric Design)가 왜 실패하는가?"**를 처절하게 경험하는 시간입니다. 저자는 좋은 설계와 나쁜 설계를 대비시켜 볼 때 통찰을 얻기 쉽다고 말합니다.

3주차 스터디를 위한 상세 가이드를 정리해 드립니다.


📅 3주차 목표: 데이터 중심 설계의 함정 파헤치기

  • 진도: 4장 (설계 품질과 트레이드오프)
  • 핵심 주제: 캡슐화(Encapsulation), 응집도(Cohesion), 결합도(Coupling)
  • 학습 목표:
    1. '책임'이 아닌 '데이터'를 중심으로 설계했을 때 발생하는 문제점(캡슐화 위반)을 코드로 확인합니다.
    2. Getter/Setter가 남발된 코드가 왜 "절차지향적"이며 "변경에 취약한지" 논리적으로 설명할 수 있어야 합니다.
    3. 좋은 설계의 척도인 캡슐화, 응집도, 결합도의 의미를 명확히 이해합니다.

💻 스터디 실습 과제 (Hands-on)

이번 주는 "나쁜 코드"를 직접 작성해보고 그 고통을 느껴보는 것이 핵심입니다.

✅ [과제 1] 데이터 중심의 영화 예매 시스템 구현하기

  • 내용: 4장에 나오는 데이터 중심의 MovieReservationAgency 코드를 직접 타이핑합니다.
  • 확인 포인트:
    • Movie 클래스에 getFee, setFee, getDiscountConditions 같은 접근자/수정자(Getter/Setter)가 가득한 것을 확인하세요.
    • 모든 로직이 ReservationAgency라는 하나의 거대한 클래스에 집중되어 있고, MovieDiscountCondition은 단지 데이터만 제공하는 수동적인 존재(데이터 덩어리)임을 확인하세요.

✅ [과제 2] '변경'의 고통 체험하기 (시뮬레이션)

  • 내용: 작성한 코드에 다음 변경 사항을 적용해 보고, 몇 개의 클래스를 고쳐야 하는지 세어봅니다.
    • 상황: "할인 조건(DiscountCondition)에 '순번'과 '기간' 외에 '요일' 조건이 추가된다면?"
  • 예상 결과:
    • DiscountCondition 클래스에 데이터를 추가해야 합니다.
    • Movie 클래스의 접근자도 수정될 수 있습니다.
    • 무엇보다 ReservationAgencyreserve 메서드 안에 있는 거대한 if-else 로직을 전부 뜯어고쳐야 합니다.
    • 결론: "데이터 중심 설계는 변경의 파급 효과를 통제하지 못한다(낮은 응집도, 높은 결합도)"는 것을 몸소 체험합니다.

✅ [과제 3] 캡슐화 위반 사례 찾기 (Rectangle 예제)

  • 내용: 책 118페이지에 나오는 Rectangle(직사각형) 클래스와 AnyClass 예제를 작성합니다.
  • 미션:
    • Rectangle의 너비와 높이를 조절하는 로직이 Rectangle 내부가 아닌 외부(AnyClass)에 존재하는 것이 왜 '코드 중복'과 '변경 취약성'을 낳는지 주석으로 정리해 봅니다.

🗣️ 스터디 토론 주제 (Deep Dive)

면접이나 실무 회의에서 설계를 논의할 때 사용할 수 있는 논리를 다듬어 봅니다.

  1. "Getter/Setter를 쓰는 게 왜 나쁜가요?"

    • 가이드: 단순히 데이터를 꺼내는 것이 문제가 아니라, "나 이런 데이터를 가지고 있어"라고 만천하에 광고하는 꼴이기 때문입니다. 이는 나중에 데이터 타입이나 저장 방식이 바뀌면, 그 데이터를 가져다 쓰는 모든 코드를 다 고쳐야 함을 의미합니다(캡슐화 위반).
  2. "캡슐화, 응집도, 결합도의 관계는?"

    • 가이드: 캡슐화를 지키면(내부 구현을 숨기면), 모듈 안의 응집도는 높아지고(관련된 일만 하게 됨), 모듈 사이의 결합도는 낮아집니다(서로 자세한 건 모름). 즉, 캡슐화가 설계 품질의 제1원칙임을 토론합니다.
  3. "절차지향과 객체지향의 결정적 차이는?"

    • 가이드: 4장의 ReservationAgency처럼 데이터와 프로세스(로직)가 분리되어 있으면 절차지향, 데이터와 그 데이터를 처리하는 프로세스가 하나의 객체 안에 통합되어 있으면 객체지향입니다.

💡 3주차 학습 팁

  • "추측하지 마세요": 데이터 중심 설계는 "이 객체에 나중에 이런 데이터가 필요하겠지?"라고 추측하며 데이터를 먼저 때려 넣는 방식입니다. 이 방식이 왜 나쁜지 이번 주에 확실히 못 박아야, 다음 주(5장)에 배울 **책임 주도 설계(어떤 행동이 필요한가?)**의 진가를 알 수 있습니다.
  • 성급한 리팩터링 금지: 4장 후반부에 리팩터링을 시도하지만 여전히 만족스럽지 않은 결과가 나옵니다. "아직도 부족하네?"라는 갈증을 느끼는 상태로 3주차를 마무리하는 것이 베스트입니다. 해결책은 4주차(5장 GRASP 패턴)에서 등장합니다.
4주차 (2026-02-12) 3주차에 **데이터 중심 설계(4장)**의 문제점인 - 캡슐화 위반 - 높은 결합도 - 낮은 응집도

를 코드로 뼈저리게 느꼈다면, 이제 그 문제를 해결할 나침반을 손에 넣을 차례입니다.

4주차는 이 책의 하이라이트 중 하나인 책임 할당GRASP 패턴을 다룹니다.


📌 4주차 목표

  • 책임 할당의 나침반 이해
  • GRASP 패턴 정복

📖 진도

  • 5장: 책임 할당하기

🎯 학습 목표

  • 데이터(구현)가 아닌 **책임(행동)**을 중심으로 설계를 시작하는 방법 이해
  • 책임을 어떤 객체에 줘야 할지 막막할 때 사용하는
    GRASP 패턴 (General Responsibility Assignment Software Patterns)
    9가지 중 핵심 패턴들을 코드로 체득
  • 4장의 절차지향 코드를 객체지향적으로 리팩터링하며
    응집도 / 결합도 개선을 직접 확인

💻 스터디 실습 과제 (Hands-on)

이번 주차는 4장의 망가진 코드 ReservationAgency
5장의 GRASP 패턴을 적용해 살려내는 과정
입니다.

⚠️ 반드시 코드를 직접 수정하면서 변화 과정을 관찰하세요.


✅ 과제 1: 정보 전문가 (Information Expert) 찾기

내용

  • 4장의 ReservationAgency에 뭉쳐 있는 로직을 각 객체로 이동
  • 책임을 가장 잘 아는 객체에게 위임

미션

  • "예매하라" 메시지를 수신했을 때
    상영 시간과 순번을 가장 잘 아는 객체는 누구인가?
    → 그 객체(Screening)에게 책임 할당
  • "할인 가능 여부를 판단하라" 메시지를 처리하기 위해
    요일, 시간 등의 데이터를 가진 객체는 누구인가?
    → 그 객체(DiscountCondition)로 로직 이동

체크 포인트

  • 핵심 로직 이동 후
    ReservationAgency 코드가 얼마나 간결해졌는지 확인

✅ 과제 2: 창조자 (Creator) 패턴 적용하기

내용

  • Reservation 객체를 누가 생성(new)하는 것이 가장 결합도가 낮은가 고민

미션

  • Reservation 생성에 필요한 정보
    • 영화(Movie)
    • 순번(Sequence)
    • 상영 시간(When)
  • 위 정보를 가장 많이 알고 있고,
    Reservation을 가장 밀접하게 사용하는 객체 찾기
  • 책의 선택: Screening
    • Screening 클래스 안에 reserve() 메서드 구현
    • new Reservation(...) 코드를 직접 작성

✅ 과제 3: 변경 보호 & 다형성 (심화)

내용

  • DiscountCondition이 변경될 때
    Movie까지 수정해야 하는 문제 해결

미션

  • 할인 조건(Sequence, Period)을 체크하는
    if-else 제거
  • DiscountCondition인터페이스로 추상화
  • 구현 클래스로 분리
    • SequenceCondition
    • PeriodCondition
  • Movie는 구체 클래스가 아닌
    DiscountCondition 인터페이스와만 협력하도록 수정

적용 패턴

  • Polymorphism
  • Protected Variations

추가 과제

  • 이 구조가 **OCP(개방-폐쇄 원칙)**를 어떻게 만족하는지
    코드 주석으로 설명

🧠 스터디 토론 주제 (Interview Prep)

면접에서

“객체지향 설계는 어떻게 하나요?”

라는 질문을 받았을 때,
GRASP 패턴을 근거로 설명할 수 있어야 합니다.

1️⃣ 로직을 어떤 객체에 넣을지 어떻게 결정하나요?

  • Information Expert 패턴 설명
  • “데이터를 가진 객체가 해당 데이터를 처리한다”
  • 예시: DiscountCondition이 스스로 할인 여부 판단

2️⃣ 설계의 품질(응집도 / 결합도)을 높이려면?

  • 서로 다른 이유로 변경되는 로직이
    한 클래스에 섞여 있을 때 발생하는 문제
  • 변경 이유에 따라 클래스를 분리하는 과정 설명

3️⃣ 절차지향 코드와 객체지향 코드의 차이는?

  • 4장 ReservationAgency
    • 데이터와 프로세스 분리 (절차지향)
  • 5장 리팩터링 코드
    • 데이터와 프로세스 통합 (객체지향)
  • 책임 이동 (Shift of Responsibility) 관점에서 비교

💡 4주차 TIP

이번 주차는 다음 원칙을 코드로 구현하는 훈련입니다.

❌ 데이터를 묻지 말고 (Getter 금지)
✅ 작업을 시켜라 (메시지 전송)

코드를 짤 때 끊임없이 질문하세요:

“이 정보를 누가 가장 잘 알고 있지?

5주차 (2026-02-25) 제공해주신 '9주 완성 플랜'과 책의 내용을 바탕으로 **5주차(6장, 8장)** 학습을 위한 **학습 목표, 실습 과제, 토론 주제**를 정리해 드립니다.

이번 주는 "객체 내부 구현을 감추고(6장), 외부 객체와의 결합을 느슨하게 만드는(8장)" 기술을 익히는 것이 핵심입니다.


📅 5주차: 좋은 인터페이스와 의존성 관리

1. 🎯 학습 목표 (Learning Objectives)

  • '디미터 법칙'과 '묻지 말고 시켜라' 원칙의 체득: 객체의 내부 구조(Getter)를 통해 데이터를 꺼내지 말고, 객체에게 원하는 작업(행동)을 요청하는 방식으로 인터페이스를 설계하는 법을 익힙니다.
  • 명령-쿼리 분리(CQS) 원칙 이해: 부수효과(Side Effect)를 일으키는 '명령'과 값을 반환하는 '쿼리'를 분리하여, 코드의 예측 가능성을 높이고 버그를 줄이는 방법을 배웁니다.
  • 컴파일타임 의존성과 런타임 의존성의 구분: 유연한 설계를 위해 코드 작성 시점(컴파일타임)의 의존성과 실행 시점(런타임)의 의존성이 달라야 함을 이해합니다.
  • 의존성 해결 기법 마스터: new 연산자를 직접 사용하여 구체 클래스에 의존하는 것이 왜 해로운지 이해하고, 이를 해결하기 위한 3가지 방법(생성자, Setter, 메서드 인자)을 능숙하게 다룰 수 있어야 합니다.

2. 💻 실습 과제 (Hands-on Practice)

과제 1: '기차 충돌(Train Wreck)' 코드 리팩터링 (6장)

  • 상황: Theater 클래스가 TicketSeller의 내부 TicketOffice에 접근하고, 다시 TicketOfficeTicket에 접근하는 식의 코드를 찾거나 작성해 봅니다. (예: ticketSeller.getTicketOffice().getTicket())
  • 미션:
    1. 디미터 법칙 위반 식별: 점(.)이 연달아 나오는 코드를 찾아보세요.
    2. '묻지 말고 시켜라' 적용: TicketSeller에게 "티켓을 팔아라(sellTo)"라고 명령하도록 코드를 수정하여 내부 구현을 캡슐화합니다.
    3. 의도를 드러내는 인터페이스: 메서드 이름을 setTicket 같은 데이터 중심의 이름에서 buy, hold, sellTo와 같이 협력의 의도가 드러나는 이름으로 변경합니다.

과제 2: new 제거 및 의존성 주입 적용 (8장)

  • 상황: Movie 클래스 내부에서 AmountDiscountPolicy 등을 new로 직접 생성하는 코드를 작성합니다.
  • 미션:
    1. 명시적 의존성으로 변환: new를 제거하고, DiscountPolicy 타입을 생성자를 통해 주입받도록 수정합니다.
    2. Setter 주입 실습: 생성된 Movie 객체의 할인 정책을 런타임에 변경할 수 있도록 Setter 메서드(changeDiscountPolicy)를 추가해 봅니다.
    3. 컨텍스트 확장 확인: 코드를 수정한 후, Movie 코드를 건드리지 않고 PercentDiscountPolicyNoneDiscountPolicy로 갈아끼우는 것이 얼마나 쉬워졌는지 확인합니다.

3. 🗣️ 토론 주제 (Discussion Topics)

주제 1: 디미터 법칙과 응집도의 트레이드오프

  • 질문: "디미터 법칙을 맹목적으로 준수하여 객체에게 '시키기만' 할 경우, 해당 객체(예: Screening)가 너무 많은 책임을 떠안아 응집도가 낮아지는 현상이 발생할 수 있습니다(책의 PeriodCondition 예시). 이럴 때는 디미터 법칙을 위반하더라도 데이터를 묻는 것이 나을까요? 여러분만의 기준은 무엇인가요?"

주제 2: 명령-쿼리 분리의 예외 상황

  • 질문: "일반적으로 스택(Stack)의 pop() 메서드는 데이터를 반환(쿼리)하면서 동시에 제거(명령/상태변경)합니다. 이처럼 CQS 원칙을 지키기 어려운 상황에서는 어떻게 설계를 타협하는 것이 좋을까요?"

주제 3: 의존성 주입(DI) 방식의 선택 기준

  • 질문: "의존성을 해결하는 3가지 방법(생성자, Setter, 메서드 인자) 중 실무에서 가장 선호하는 방식은 무엇이며 그 이유는 무엇인가요? 책에서는 '생성자 주입'을 가장 권장하는데, Setter 주입이 꼭 필요한 경우는 언제일까요?"

주제 4: 표준 클래스에 대한 의존성

  • 질문: "책에서는 ArrayListString 같은 표준 라이브러리 클래스는 직접 new로 생성해도 문제가 없다고 말합니다. 구체 클래스에 의존하는 것이 위험한 이유는 '변경' 때문인데, 우리가 만드는 도메인 코드와 표준 라이브러리의 결정적인 차이는 무엇일까요?"
6주차 (2026-03-05) ---

📅 6주차: 유연한 설계와 SOLID (9장)

1. 🎯 학습 목표 (Learning Objectives)

  • 개방-폐쇄 원칙(OCP)의 메커니즘 이해: OCP가 단순히 "확장엔 열려있고 수정엔 닫혀있다"는 구호가 아니라, '컴파일타임 의존성'을 고정시키고 '런타임 의존성'을 변경하는 기술적인 메커니즘임을 이해합니다.
  • 생성과 사용의 분리(Separation of Use from Creation): 객체가 협력할 대상을 직접 생성(new)하면 결합도가 높아짐을 깨닫고, 이를 해결하기 위해 객체 생성 책임을 클라이언트나 FACTORY로 옮기는 법을 배웁니다.
  • 의존성 주입(DI)과 명시적 의존성: 의존성 주입의 3가지 방식(생성자, Setter, 메서드)을 구분하고, **숨겨진 의존성(Service Locator)**이 왜 캡슐화를 위반하고 코드를 이해하기 어렵게 만드는지 설명할 수 있어야 합니다.
  • 의존성 역전 원칙(DIP)과 패키지: 상위 수준 모듈이 하위 수준 모듈에 의존하는 전통적인 구조를 뒤집어, 둘 다 추상화에 의존하게 만드는 원리를 배우고 이를 패키지 구조에 적용해 봅니다.

2. 💻 실습 과제 (Hands-on Practice)

과제 1: new의 해로움 체험 및 FACTORY 도입

  • 상황: Movie 클래스 내부에서 AmountDiscountPolicynew 연산자로 직접 생성하는 코드를 작성합니다.
  • 미션:
    1. 문제점 확인: 할인 정책을 변경하려면 Movie 코드를 수정해야 함을 확인합니다(OCP 위반).
    2. 책임 이관: Movie 내부의 생성 로직을 제거하고, Client가 생성해서 Movie 생성자에 전달하도록 수정합니다.
    3. FACTORY 패턴 적용: 객체 생성 책임이 Client에 노출되는 것을 막기 위해, 생성만 전담하는 Factory 클래스(PURE FABRICATION)를 만들어 책임을 위임해 봅니다.

과제 2: 숨겨진 의존성(Service Locator) 리팩터링

  • 상황: 의존성 주입 대신, ServiceLocator라는 전역 객체를 통해 의존성을 해결하는 코드를 작성합니다.
  • 미션:
    1. 런타임 에러 유발: ServiceLocator에 의존성을 등록하지 않은 상태로 코드를 실행하여 NullPointerException을 발생시켜 봅니다. 퍼블릭 인터페이스만 봐서는 무엇이 필요한지 알 수 없다는 문제(숨겨진 의존성)를 체감합니다.
    2. 명시적 의존성으로 전환: 생성자 주입(Constructor Injection) 방식으로 변경하여, 필요한 의존성을 퍼블릭 인터페이스(생성자 파라미터)에 명확히 드러냅니다.

과제 3: 의존성 역전(DIP)과 패키지 분리

  • 상황: Movie가 구체적인 AmountDiscountPolicy 클래스에 의존하는 구조를 상상해 봅니다.
  • 미션:
    1. 인터페이스 도입: DiscountPolicy 추상 클래스(또는 인터페이스)를 도입하여 Movie가 추상화에 의존하게 만듭니다.
    2. 패키지 구조 변경: MovieDiscountPolicy 인터페이스를 같은 패키지(상위 수준)에 두고, 구현체인 AmountDiscountPolicy를 별도 패키지(하위 수준)로 분리하여 SEPARATED INTERFACE 패턴을 적용해 봅니다.

3. 🗣️ 토론 주제 (Discussion Topics)

주제 1: 유연성과 복잡성의 트레이드오프

  • 질문: "9장의 기법들(FACTORY, DI, 추상화 등)을 적용하면 유연성은 높아지지만, 코드의 구조는 더 복잡해지고 흐름을 파악하기 어려워집니다(코드와 실행 시점의 구조가 다름). 여러분은 **'변경 가능성'**이 불확실한 상황에서도 미리 유연한 설계를 적용하시겠습니까, 아니면 변경이 발생할 때 리팩터링하시겠습니까?"

주제 2: 프레임워크와 의존성 주입

  • 질문: "스프링(Spring) 같은 프레임워크는 의존성 주입을 컨테이너가 대신 해줍니다. 책에서 배운 '생성과 사용의 분리' 관점에서 볼 때, 프레임워크는 어떤 역할을 수행하는 것일까요? (힌트: 객체 조립의 책임)"

주제 3: 추상화의 올바른 위치

  • 질문: "의존성 역전 원칙(DIP)에 따르면 인터페이스(추상화)는 클라이언트(Movie)가 속한 패키지에 있어야 합니다. 하지만 실무에서는 인터페이스와 구현체를 같은 패키지에 두거나, 별도의 'common' 패키지에 두는 경우가 많습니다. 책의 원칙과 실무 관행 사이의 차이는 왜 발생할까요?"
7주차 (2026-03-12)

이제 '4단계: 상속의 한계와 합성' 패러다임 전환의 첫걸음인 7주차(10장: 상속과 코드 재사용) 학습 가이드를 정리해 드립니다. 이번 주는 **"코드 재사용을 위해 상속을 썼을 때 치러야 하는 끔찍한 대가"**를 직접 체험하는 시간입니다.


📅 7주차: 상속의 문제점 체험 (10장)

1. 🎯 학습 목표 (Learning Objectives)

  • DRY 원칙의 진정한 의미 체득: '반복하지 마라(Don't Repeat Yourself)' 원칙을 통해 중복 코드가 어떻게 변경을 방해하고 시스템을 공황 상태로 몰아넣는지 이해합니다.
  • 상속의 치명적 단점 이해: 상속이 단순히 코드를 복사하는 편한 도구가 아니라, 부모 클래스의 내부 구현에 자식 클래스를 강하게 결합시켜 캡슐화를 위반하게 만든다는 것을 배웁니다,.
  • 취약한 기반 클래스 문제(Fragile Base Class Problem) 파악: 부모 클래스의 작은 변경이 자식 클래스를 연쇄적으로 망가뜨리거나, 부모와 자식 클래스를 동시에 수정해야만 하는 문제를 파악합니다.

2. 💻 실습 과제 (Hands-on Practice)

과제 1: 상속으로 인한 '세금 폭탄' 지옥 체험하기

  • 상황: 10장에 나오는 일반 요금제(Phone)와 심야 할인 요금제(NightlyDiscountPhone)의 코드를 작성합니다,. 중복 코드를 없애기 위해 NightlyDiscountPhonePhone을 상속받도록 만듭니다.
  • 미션:
    1. 요구사항 변경: 통화 요금에 세금(taxRate)을 부과하는 기능을 추가해 보세요.
    2. 결과 확인: 부모 클래스인 Phone에 세금 인스턴스 변수와 생성자를 추가했을 때, 자식 클래스인 NightlyDiscountPhone의 생성자까지 줄줄이 에러가 나며 수정해야 하는 상황을 눈으로 확인합니다,. 이를 통해 상속 계층 전체에 걸쳐 변경이 퍼져나가는 파급효과를 체감합니다.

과제 2: 자바(Java) 표준 라이브러리의 흑역사 파헤치기

  • 상황: 불필요한 인터페이스 상속의 대표적 사례인 java.util.Stackjava.util.Properties를 살펴봅니다.
  • 미션:
    1. IDE에서 Stack<String> 객체를 생성합니다.
    2. 스택의 기본 원칙인 LIFO(후입선출)를 무시하고, 부모 클래스인 Vector에서 상속받은 add(index, element) 메서드를 사용해 스택의 맨 밑이나 중간에 임의로 데이터를 밀어 넣어 보세요.
    3. 부모의 인터페이스가 자식의 내부 규칙(무결성)을 어떻게 파괴하는지 확인합니다.

3. 🗣️ 토론 주제 (Discussion Topics)

주제 1: [면접 대비] 상속의 단점을 설명해 보세요

  • 질문: "면접관이 '코드 중복을 줄이기 위해 상속을 사용하는 것에 대해 어떻게 생각하시나요? 상속의 단점을 설명해 주세요'라고 질문했습니다. 10장에서 배운 **'결합도'**와 **'캡슐화 위반'**의 관점을 들어 논리적으로 답변해 보세요."

주제 2: 트레이드오프 - 개발 속도 vs 설계 품질

  • 질문: "상속은 가장 빠르고 쉽게 새로운 클래스를 추가할 수 있는 방법임이 틀림없습니다. 만약 내일 당장 급하게 새로운 할인 요금제를 출시해야 한다면, 기술 부채(취약한 기반 클래스 문제 등)를 감수하고서라도 복붙이나 상속을 통해 빠르게 구현하시겠습니까? 아니면 일정을 미루더라도 처음부터 유연한 설계(합성 등)를 고집하시겠습니까?"

주제 3: 메서드 오버라이딩의 오작용

  • 질문: "자식 클래스가 부모 클래스의 메서드를 오버라이딩(Overriding)할 때, 부모 클래스가 내부적으로 자신의 메서드를 어떻게 호출하고 있는지(self 전송) 알지 못하면 심각한 버그(InstrumentedHashSet 예시)가 발생할 수 있습니다. 상속을 위해 부모 클래스의 내부 구현까지 샅샅이 파악해야 한다면, 과연 객체지향의 핵심인 **정보 은닉(Information Hiding)**은 상속 앞에서 무용지물이 되는 걸까요?"
8주차 (2026-03-19) 이번 주는 이전 주차(10장)에서 경험했던 상속의 치명적인 단점(클래스 폭발)을 **합성(Composition)**이라는 우아한 방법으로 해결하며, 왜 객체지향에서 "상속보다 합성을 선호하라"고 하는지 코드로 직접 증명하는 주간입니다. ---

📅 8주차: 합성을 통한 유연성 확보 (11장)

"상속은 '화이트박스' 재사용이고, 합성은 '블랙박스' 재사용이다."

1. 🎯 학습 목표 (Learning Objectives)

  • 상속과 합성의 본질적 차이 이해: 상속은 '컴파일타임'에 부모-자식 클래스의 구현이 강하게 결합되는 정적인 관계인 반면, 합성은 '런타임'에 퍼블릭 인터페이스를 통해 약하게 결합되는 동적인 관계임을 이해합니다.
  • 클래스 폭발(Class Explosion) 문제 파악: 여러 기능을 조합해야 할 때 상속을 사용하면, 필요한 모든 조합의 수만큼 새로운 클래스를 만들어야 하는 문제점을 파악합니다.
  • 합성을 통한 유연한 조립: 부가 정책(세금 정책, 할인 정책 등)을 독립적인 객체로 만들고, 이들을 실행 시점에 레고 블록처럼 유연하게 조립(합성)하는 방법을 익힙니다.

2. 💻 실습 과제 (Hands-on Practice)

과제 1: 10장의 상속 코드를 합성으로 변경하기

  • 상황: 10장에서 java.util.Propertiesjava.util.Stack이 불필요한 인터페이스를 상속받아 문제가 생겼던 코드를 떠올려 봅니다.
  • 미션:
    1. 상속 관계(extends)를 과감히 제거합니다.
    2. 부모 클래스의 인스턴스를 자식 클래스의 **인스턴스 변수로 포함(합성)**시킵니다.
    3. 퍼블릭 인터페이스(메시지)를 통해서만 내부 객체와 협력하도록 리팩터링하여, 기존 상속이 야기했던 캡슐화 위반 문제가 어떻게 해결되는지 눈으로 확인합니다.

과제 2: 핸드폰 과금 시스템 '정책 조합' 마법 체험하기

  • 상황: 기본 정책(일반 요금제, 심야 할인)에 부가 정책(세금, 기본 요금 할인)을 자유롭게 조합하려고 합니다. 10장에서는 이를 위해 TaxableAndRateDiscountableRegularPhone 같이 무수히 많은 클래스(클래스 폭발)를 만들어야 했습니다.
  • 미션:
    1. 인터페이스 도입: 요금 계산 로직을 RatePolicy 인터페이스로 추상화합니다.
    2. 합성 기반 설계: Phone 클래스가 특정 구체 클래스를 상속받지 않고, RatePolicy를 생성자로 주입받아 내부 변수로 가지도록 수정합니다.
    3. 런타임 조합: new Phone(new TaxablePolicy(new RateDiscountablePolicy(new RegularPolicy(...))))와 같이 실행 시점에 객체들을 겹겹이 포장(Decorator 패턴 형태)하여 요금제를 자유자재로 만들어 봅니다. 코드를 수정하지 않고도 객체 조합만으로 새로운 기능을 창조하는 경험을 맛보세요.

3. 🗣️ 토론 주제 (Discussion Topics)

주제 1: 상속 vs 합성, 합성이 항상 정답일까?

  • 질문: "책에서는 '코드 재사용을 위해서는 상속보다 합성을 선호하라'고 강조합니다. 하지만 합성을 사용하면 컴파일타임 의존성과 런타임 의존성이 멀어지면서 설계가 복잡해지고 코드를 따라가며 읽기(디버깅) 어려워진다는 단점도 존재합니다. 여러분은 실무에서 **구현의 편리함(상속)**과 설계의 유연성(합성) 중 어느 쪽에 더 가중치를 두시나요? 그 이유는 무엇인가요?"

주제 2: 스프링 프레임워크와 합성의 관계

  • 질문: "스프링(Spring)과 같은 프레임워크에서 사용하는 핵심 기술인 의존성 주입(DI)은 결국 객체와 객체를 '합성(Composition)'하는 과정입니다. 스프링 빈(Bean)들을 조립하는 방식을 이번 11장에서 배운 합성의 개념(실행 시점의 동적인 관계 연결)에 비추어 설명해 본다면 어떨까요?"

주제 3: '믹스인(Mixin)'과 다중 상속 (※ 책의 믹스인 파트를 읽은 경우)

  • 질문: "자바 같은 언어는 단일 상속만 지원하기 때문에 클래스 폭발 문제가 더욱 두드러집니다. 스칼라의 트레이트(Trait)처럼 코드를 유연하게 섞어 넣는 '믹스인' 기법을 자바 환경(예: Java 8 이상의 디폴트 메서드 등)에서는 어떻게 흉내 내거나 적용할 수 있을까요?"
9주차 (2026-03-26) 이번 주는 이전 주차에서 "상속을 피하라"고 배웠던 것을 넘어, **"그렇다면 상속은 도대체 언제, 어떻게 써야 하는가?"**에 대한 명확한 해답(타입 계층)을 내리는 매우 중요한 주차입니다. 그 핵심 기준이 바로 **리스코프 치환 원칙(LSP)**입니다.

📅 9주차: 리스코프 치환 원칙과 올바른 상속 (13장)

"상속의 목적은 코드 재사용이 아니라, 타입 계층을 구축하는 것이다."

1. 🎯 학습 목표 (Learning Objectives)

  • 서브클래싱과 서브타이핑의 구분: 코드를 재사용할 목적으로 상속을 쓰는 '서브클래싱(구현 상속)'과, 올바른 타입 계층을 구성하기 위해 상속을 쓰는 '서브타이핑(인터페이스 상속)'의 차이를 명확히 구분합니다.
  • 클라이언트 관점과 행동 호환성: 객체의 타입은 내부 속성이나 현실 세계의 어휘가 아니라, 철저히 **'클라이언트가 기대하는 행동'**에 의해 결정됨을 이해합니다.
  • 리스코프 치환 원칙(LSP) 체득: 자식 클래스가 부모 클래스를 대체하기 위해서는 부모 클래스에 대한 클라이언트의 가정을 준수해야 한다는 원칙을 배웁니다.
  • 계약에 의한 설계(DBC)와 LSP: 사전조건과 사후조건의 관점에서 서브타입이 부모의 계약을 어떻게 유지해야 하는지(사전조건 강화 불가, 사후조건 약화 불가) 파악합니다.

2. 💻 실습 과제 (Hands-on Practice)

과제 1: '직사각형-정사각형'의 배신 (LSP 위반 체험)

  • 상황: 13장에 등장하는 Rectangle(직사각형)과 Square(정사각형) 클래스를 직접 구현해 봅니다.
  • 미션:
    1. 자연어의 직관(is-a)에 따라 SquareRectangle을 상속(extends)받도록 작성하고, SquaresetWidth, setHeight 오버라이딩 시 너비와 높이가 항상 같도록 강제합니다.
    2. 클라이언트의 가정 무너뜨리기: 클라이언트 코드에서 Rectangle의 너비와 높이를 다르게 설정(resize 메서드)하도록 작성한 뒤, 그 파라미터에 Square 인스턴스를 넘겨봅니다.
    3. 결과 확인: 클라이언트가 "너비와 높이가 다를 수 있다"고 세운 가정이 무너지면서 코드가 오작동하는 것을 확인하고, "정사각형은 직사각형이다"라는 어휘적 is-a 관계가 소프트웨어에서는 거짓일 수 있음을 체감합니다.

과제 2: 클라이언트 기대에 따른 상속 계층 분리 ('펭귄과 새')

  • 상황: Bird 클래스(fly 메서드 포함)를 상속받는 Penguin 클래스가 있습니다.
  • 미션:
    1. 펭귄은 날 수 없으므로 fly 오버라이딩을 비워두거나 에러를 던지게 해봅니다. 이것이 클라이언트의 기대(fly 호출)를 배신하는 LSP 위반임을 확인합니다.
    2. 계층 분리: 클라이언트의 기대에 맞춰 FlyingBird 클래스를 새로 만들거나, 인터페이스를 분리하여 "날 수 있는 새"와 "날 수 없는 새"가 완벽한 행동 호환성을 갖도록 리팩터링해 봅니다.

3. 🗣️ 토론 주제 (Discussion Topics)

주제 1: 현실 세계의 'is-a' vs 객체지향의 'is-a'

  • 질문: "현실 세계에서는 분명히 '정사각형은 직사각형이다'나 '펭귄은 새다'가 성립하지만, 객체지향에서는 **클라이언트의 기대(행동)**에 따라 이 관계가 깨질 수 있습니다. 책에서는 문장 앞에 *(클라이언트 입장에서)*를 붙여서 생각하라고 조언합니다. 여러분은 실무에서 도메인의 현실적인 개념 분류와 소프트웨어 클라이언트의 요구사항이 충돌할 때, 어떤 기준으로 타입 계층을 설계하시나요?"

주제 2: 상속의 두 얼굴, 서브클래싱 vs 서브타이핑

  • 질문: "책에서는 코드 재사용을 위한 상속(서브클래싱)을 엄격히 지양하고, 다형적인 타입 계층을 위한 상속(서브타이핑)만을 올바른 상속으로 인정합니다. 그렇다면 단순히 '중복 코드'를 줄이고 싶은 상황이 발생했을 때, 여러분은 상속의 유혹을 뿌리치고 주로 어떤 대안(예: 합성, 위임(Delegation), 헬퍼 클래스 등)을 선택하시나요?"

주제 3: 계약에 의한 설계와 새로운 예외(Exception)

  • 질문: "LSP를 계약에 의한 설계 관점에서 보면 '서브타입은 더 강력한 사전조건을 요구할 수 없다'고 합니다. 만약 자식 클래스에서 부모 클래스의 메서드를 오버라이딩하면서 부모에는 없던 새로운 유효성 검사를 추가하고 새로운 예외(Exception)를 던진다면, 이것은 LSP 위반일까요? 실무에서 이런 상황(요구사항 변경으로 자식 쪽에 제약이 추가될 때)을 어떻게 처리하는 것이 안전할까요?"

About

오브젝트: 코드로 이해하는 객체지향 설계 예제

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Java 89.8%
  • Ruby 3.6%
  • C# 3.5%
  • Scala 2.2%
  • Other 0.9%