🚀
Incheol's TECH BLOG
  • Intro
  • Question & Answer
    • JAVA
      • JVM
      • String, StringBuffer, StringBuilder
      • JDK 17일 사용한 이유(feat. JDK 8 이후 훑어보기)
      • 스택 오버 플로우(SOF)
      • 블럭킹 | 논블럭킹 | 동기 | 비동기
      • 병렬처리를 이용한 이미지 리사이즈 개선
      • heap dump 분석하기 (feat. OOM)
      • G1 GC vs Z GC
      • JIT COMPILER
      • ENUM
      • STATIC
      • Thread(쓰레드)
      • hashCode()와 equals()
      • JDK 8 특징
      • break 와 continue 사용
      • STREAM
      • Optional
      • 람다와 클로저
      • Exception(예외)
      • Garbage Collector
      • Collection
      • Call by Value & Call by Reference
      • 제네릭(Generic)
    • SPRING
      • Spring 특징
      • N+1 문제
      • 테스트 코드 어디까지 알아보고 오셨어요?
      • 테스트 코드 성능 개선기
      • RestTemplate 사용시 주의사항
      • 동시성 해결하기(feat. TMI 주의)
      • redisson trylock 내부로직 살펴보기
      • DB 트래픽 분산시키기(feat. Routing Datasource)
      • OSIV
      • @Valid 동작 원리
      • mybatis @Builder 주의사항
      • 스프링 클라우드 컨피그 갱신 되지 않는 이슈(feat. 서비스 디스커버리)
      • ImageIO.read 동작하지 않는 경우
      • 카프카 transaction 처리는 어떻게 해야할까?
      • Spring Boot 특징
      • Spring 5 특징
      • JPA vs MyBatis
      • Filter와 Interceptor
      • 영속성 컨텍스트(Persistence Context)
      • @Transactional
      • @Controlleradvice, @ExceptionHandler
      • Spring Security
      • Dispatcher Servlet
      • @EnableWebMvc
      • Stereo Type(스테레오 타입)
      • AOP
      • JPA Repository 규칙
    • DATABASE
      • Database Index
      • SQL vs NoSQL
      • DB 교착상태
      • Isolation level
      • [MySQL] 이모지 저장은 어떻게 하면 좋을까?
      • SQL Hint
      • JOIN
    • INFRA
      • CLOUD COMPUTING
      • GIT
      • DOCKER
      • 카프카 찍먹하기 1부
      • 카프카 찍먹하기 2부 (feat. 프로듀서)
      • 카프카 찍먹하기 3부 (feat. 컨슈머)
      • JENKINS
      • POSTMAN
      • DNS 동작 원리
      • ALB, NLB,ELB 차이는?
      • 카프카 파티션 주의해서 사용하자
      • DEVOPS
      • JWT
      • OSI 7 Layer
      • MSA
      • 서비스 디스커버리는 어떻게 서비스 등록/해제 하는걸까?
      • 핀포인트 사용시 주의사항!! (feat 로그 파일 사이즈)
      • AWS EC2 도메인 설정 (with ALB)
      • ALB에 SSL 설정하기(feat. ACM)
      • 람다를 활용한 클라우드 와치 알림 받기
      • AWS Personalize 적용 후기… 😰
      • CloudFront를 활용한 S3 성능 및 비용 개선
    • ARCHITECTURE
      • 객체지향과 절차지향
      • 상속보단 합성
      • SOLID 원칙
      • 캡슐화
      • DDD(Domain Driven Design)
    • COMPUTER SCIENCE
      • 뮤텍스와 세마포어
      • Context Switch
      • REST API
      • HTTP HEADER
      • HTTP METHOD
      • HTTP STATUS
    • CULTURE
      • AGILE(Feat. 스크럼)
      • 우리는 성장 할수 있을까? (w. 함께 자라기)
      • Expert Beginner
    • SEMINAR
      • 2022 INFCON 후기
        • [104호] 사이드 프로젝트 만세! - 기술만큼 중요했던 제품과 팀 성장기
        • [102호] 팀을 넘어서 전사적 협업 환경 구축하기
        • [103호] 코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes
        • [105호] 실전! 멀티 모듈 프로젝트 구조와 설계
        • [105호] 지금 당장 DevOps를 해야 하는 이유
        • [102호] (레거시 시스템) 개편의 기술 - 배달 플랫폼에서 겪은 N번의 개편 경험기
        • [102호] 서버비 0원, 클라우드 큐 도입으로 해냈습니다!
  • STUDY
    • 오브젝트
      • 1장 객체, 설계
      • 2장 객체지향 프로그래밍
      • 3장 역할, 책임, 협력
      • 4장 설계 품질과 트레이드 오프
      • 5장 책임 할당하기
      • 6장 메시지와 인터페이스
      • 7징 객체 분해
      • 8장 의존성 관리하기
      • 9장 유연한 설계
      • 10장 상속과 코드 재사용
      • 11장 합성과 유연한 설계
      • 12장 다형성
      • 13장 서브클래싱과 서브타이핑
      • 14장 일관성 있는 협력
      • 15장 디자인 패턴과 프레임워크
      • 마무리
    • 객체지향의 사실과 오해
      • 1장 협력하는 객체들의 공동체
      • 2장 이상한 나라의 객체
      • 3장 타입과 추상화
      • 4장 역할, 책임, 협력
    • JAVA ORM JPA
      • 1장 JPA 소개
      • 2장 JPA 시작
      • 3장 영속성 관리
      • 4장 엔티티 매핑
      • 5장 연관관계 매핑 기초
      • 6장 다양한 연관관계 매핑
      • 7장 고급 매핑
      • 8장 프록시와 연관관계 관리
      • 9장 값 타입
      • 10장 객체지향 쿼리 언어
      • 11장 웹 애플리케이션 제작
      • 12장 스프링 데이터 JPA
      • 13장 웹 애플리케이션과 영속성 관리
      • 14장 컬렉션과 부가 기능
      • 15장 고급 주제와 성능 최적화
      • 16장 트랜잭션과 락, 2차 캐시
    • 토비의 스프링 (3.1)
      • 스프링의 이해와 원리
        • 1장 오브젝트와 의존관계
        • 2장 테스트
        • 3장 템플릿
        • 4장 예외
        • 5장 서비스 추상화
        • 6장 AOP
        • 8장 스프링이란 무엇인가?
      • 스프링의 기술과 선택
        • 5장 AOP와 LTW
        • 6장 테스트 컨텍스트 프레임워크
    • 클린코드
      • 1장 깨끗한 코드
      • 2장 의미 있는 이름
      • 3장 함수
      • 4장 주석
      • 5장 형식 맞추기
      • 6장 객체와 자료 구조
      • 9장 단위 테스트
    • 자바 트러블슈팅(with scouter)
      • CHAP 01. 자바 기반의 시스템에서 발생할 수 있는 문제들
      • CHAP 02. scouter 살펴보기
      • CHAP 03. scouter 설정하기(서버 및 에이전트)
      • CHAP 04. scouter 클라이언트에서 제공하는 기능들
      • CHAP 05. scouter XLog
      • CHAP 06. scouter 서버/에이전트 플러그인
      • CHAP 07. scouter 사용 시 유용한 팁
      • CHAP 08. 스레드 때문에(스레드에서) 발생하는 문제들
      • CHAP 09. 스레드 단면 잘라 놓기
      • CHAP 10. 잘라 놓은 스레드 단면 분석하기
      • CHAP 11. 스레드 문제
      • CHAP 12. 메모리 때문에 발생할 수 있는 문제들
      • CHAP 13. 메모리 단면 잘라 놓기
      • CHAP 14. 잘라 놓은 메모리 단면 분석하기
      • CHAP 15. 메모리 문제(Case Study)
      • CHAP 24. scouter로 리소스 모니터링하기
      • CHAP 25. 장애 진단은 이렇게 한다
      • 부록 A. Fatal error log 분석
      • 부록 B. 자바 인스트럭션
    • 테스트 주도 개발 시작하기
      • CHAP 02. TDD 시작
      • CHAP 03. 테스트 코드 작성 순서
      • CHAP 04. TDD/기능 명세/설계
      • CHAP 05. JUnit 5 기초
      • CHAP 06. 테스트 코드의 구성
      • CHAP 07. 대역
      • CHAP 08. 테스트 가능한 설계
      • CHAP 09. 테스트 범위와 종류
      • CHAP 10. 테스트 코드와 유지보수
      • 부록 A. Junit 5 추가 내용
      • 부록 C. Mockito 기초 사용법
      • 부록 D. AssertJ 소개
    • KOTLIN IN ACTION
      • 1장 코틀린이란 무엇이며, 왜 필요한가?
      • 2장 코틀린 기초
      • 3장 함수 정의와 호출
      • 4장 클래스, 객체, 인터페이스
      • 5장 람다로 프로그래밍
      • 6장 코틀린 타입 시스템
      • 7장 연산자 오버로딩과 기타 관례
      • 8장 고차 함수: 파라미터와 반환 값으로 람다 사용
      • 9장 제네릭스
      • 10장 애노테이션과 리플렉션
      • 부록 A. 코틀린 프로젝트 빌드
      • 부록 B. 코틀린 코드 문서화
      • 부록 D. 코틀린 1.1과 1.2, 1.3 소개
    • KOTLIN 공식 레퍼런스
      • BASIC
      • Classes and Objects
        • Classes and Inheritance
        • Properties and Fields
    • 코틀린 동시성 프로그래밍
      • 1장 Hello, Concurrent World!
      • 2장 코루틴 인 액션
      • 3장 라이프 사이클과 에러 핸들링
      • 4장 일시 중단 함수와 코루틴 컨텍스트
      • 5장 이터레이터, 시퀀스 그리고 프로듀서
      • 7장 스레드 한정, 액터 그리고 뮤텍스
    • EFFECTIVE JAVA 3/e
      • 객체 생성과 파괴
        • 아이템1 생성자 대신 정적 팩터리 메서드를 고려하라
        • 아이템2 생성자에 매개변수가 많다면 빌더를 고려하라
        • 아이템3 private 생성자나 열거 타입으로 싱글턴임을 보증하라
        • 아이템4 인스턴스화를 막으려거든 private 생성자를 사용하라
        • 아이템5 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
        • 아이템6 불필요한 객체 생성을 피하라
        • 아이템7 다 쓴 객체 참조를 해제하라
        • 아이템8 finalizer와 cleaner 사용을 피하라
        • 아이템9 try-finally보다는 try-with-resources를 사용하라
      • 모든 객체의 공통 메서드
        • 아이템10 equals는 일반 규약을 지켜 재정의하라
        • 아이템11 equals를 재정의 하려거든 hashCode도 재정의 하라
        • 아이템12 toString을 항상 재정의하라
        • 아이템13 clone 재정의는 주의해서 진행해라
        • 아이템14 Comparable을 구현할지 고려하라
      • 클래스와 인터페이스
        • 아이템15 클래스와 멤버의 접근 권한을 최소화하라
        • 아이템16 public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
        • 아이템17 변경 가능성을 최소화하라
        • 아이템18 상속보다는 컴포지션을 사용하라
        • 아이템19 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라
        • 아이템20 추상 클래스보다는 인터페이스를 우선하라
        • 아이템21 인터페이스는 구현하는 쪽을 생각해 설계하라
        • 아이템22 인터페이스 타입을 정의하는 용도로만 사용하라
        • 아이템23 태그 달린 클래스보다는 클래스 계층구조를 활용하라
        • 아이템24 멤버 클래스는 되도록 static으로 만들라
        • 아이템25 톱레벨 클래스는 한 파일에 하나만 담으라
      • 제네릭
        • 아이템26 로 타입은 사용하지 말라
        • 아이템27 비검사 경고를 제거하라
        • 아이템28 배열보다는 리스트를 사용하라
        • 아이템29 이왕이면 제네릭 타입으로 만들라
        • 아이템30 이왕이면 제네릭 메서드로 만들라
        • 아이템31 한정적 와일드카드를 사용해 API 유연성을 높이라
        • 아이템32 제네릭과 가변인수를 함께 쓸 때는 신중하라
        • 아이템33 타입 안전 이종 컨테이너를 고려하라
      • 열거 타입과 애너테이션
        • 아이템34 int 상수 대신 열거 타입을 사용하라
        • 아이템35 ordinal 메서드 대신 인스턴스 필드를 사용하라
        • 아이템36 비트 필드 대신 EnumSet을 사용하라
        • 아이템37 ordinal 인덱싱 대신 EnumMap을 사용하라
        • 아이템38 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
        • 아이템 39 명명 패턴보다 애너테이션을 사용하라
        • 아이템40 @Override 애너테이션을 일관되게 사용하라
        • 아이템41 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라
      • 람다와 스트림
        • 아이템46 스트림에는 부작용 없는 함수를 사용하라
        • 아이템47 반환 타입으로는 스트림보다 컬렉션이 낫다
        • 아이템48 스트림 병렬화는 주의해서 적용하라
      • 메서드
        • 아이템49 매개변수가 유효한지 검사하라
        • 아이템50 적시에 방어적 본사본을 만들라
        • 아이템53 가변인수는 신중히 사용하라
        • 아이템 54 null이 아닌, 빈 컬렉션이나 배열을 반환하라
        • 아이템56 공개된 API 요소에는 항상 문서화 주석을 작성하라
      • 일반적인 프로그래밍 원칙
        • 아이템56 공개된 API 요소에는 항상 문서화 주석을 작성하라
        • 아이템57 지역변수의 범위를 최소화하라
        • 아이템 60 정확한 답이 필요하다면 float와 double은 피하라
      • 예외
        • 아이템 73 추상화 수준에 맞는 예외를 던지라
        • 아이템 74 메서드가 던지는 모든 예외를 문서화하라
      • 동시성
        • 아이템78 공유 중인 가변 데이터는 동기화해 사용하라
        • 아이템79 과도한 동기화는 피하라
        • 아이템 80 스레드보다는 실행자, 태스크, 스트림을 애용하라
      • 직렬화
        • 아이템 87 커스텀 직렬화 형태를 고려해보라
    • Functional Programming in Java
      • Chap 01. 헬로, 람다 표현식
      • Chap 02. 컬렉션의 사용
      • Chap 03. String, Comparator, 그리고 filter
      • Chap 04. 람다 표현식을 이용한 설계
      • CHAP 05. 리소스를 사용한 작업
      • CHAP 06. 레이지
      • CHAP 07. 재귀 호출 최적화
      • CHAP 08. 람다 표현식의 조합
      • CHAP 09. 모든 것을 함께 사용해보자
      • 부록 1. 함수형 인터페이스의 집합
      • 부록 2. 신택스 오버뷰
    • 코틀린 쿡북
      • 2장 코틀린 기초
      • 3장 코틀린 객체지향 프로그래밍
      • 4장 함수형 프로그래밍
      • 5장 컬렉션
      • 6장 시퀀스
      • 7장 영역 함수
      • 9장 테스트
      • 10장 입력/출력
      • 11장 그 밖의 코틀린 기능
    • DDD START!
      • 1장 도메인 모델 시작
      • 2장 아키텍처 개요
      • 3장 애그리거트
      • 4장 리포지터리와 모델구현(JPA 중심)
      • 5장 리포지터리의 조회 기능(JPA 중심)
      • 6장 응용 서비스와 표현 영역
      • 7장 도메인 서비스
      • 8장 애그리거트 트랜잭션 관리
      • 9장 도메인 모델과 BOUNDED CONTEXT
      • 10장 이벤트
      • 11장 CQRS
    • JAVA 8 IN ACTION
      • 2장 동작 파라미터화 코드 전달하기
      • 3장 람다 표현식
      • 4장 스트림 소개
      • 5장 스트림 활용
      • 6장 스트림으로 데이터 수집
      • 7장 병렬 데이터 처리와 성능
      • 8장 리팩토링, 테스팅, 디버깅
      • 9장 디폴트 메서드
      • 10장 null 대신 Optional
      • 11장 CompletableFuture: 조합할 수 있는 비동기 프로그래밍
      • 12장 새로운 날짜와 시간 API
      • 13장 함수형 관점으로 생각하기
      • 14장 함수형 프로그래밍 기법
    • 객체지향과 디자인패턴
      • 객체 지향
      • 다형성과 추상 타입
      • 재사용: 상속보단 조립
      • 설계 원칙: SOLID
      • DI와 서비스 로케이터
      • 주요 디자인 패턴
        • 전략패턴
        • 템플릿 메서드 패턴
        • 상태 패턴
        • 데코레이터 패턴
        • 프록시 패턴
        • 어댑터 패턴
        • 옵저버 패턴
        • 파사드 패턴
        • 추상 팩토리 패턴
        • 컴포지트 패턴
    • NODE.JS
      • 1회차
      • 2회차
      • 3회차
      • 4회차
      • 6회차
      • 7회차
      • 8회차
      • 9회차
      • 10회차
      • 11회차
      • 12회차
      • mongoose
      • AWS란?
    • SRPING IN ACTION (5th)
      • Chap1. 스프링 시작하기
      • Chap 2. 웹 애플리케이션 개발하기
      • Chap 3. 데이터로 작업하기
      • Chap 4. 스프링 시큐리티
      • Chap 5. 구성 속성 사용하기
      • Chap 6. REST 서비스 생성하기
      • Chap 7. REST 서비스 사용하기
      • CHAP 8 비동기 메시지 전송하기
      • Chap 9. 스프링 통합하기
      • CHAP 10. 리액터 개요
      • CHAP 13. 서비스 탐구하기
      • CHAP 15. 실패와 지연 처리하기
      • CHAP 16. 스프링 부트 액추에이터 사용하기
    • 스프링부트 코딩 공작소
      • 스프링 부트를 왜 사용 해야 할까?
      • 첫 번째 스프링 부트 애플리케이션 개발하기
      • 구성을 사용자화 하기
      • 스프링부트 테스트하기
      • 액추에이터로 내부 들여다보기
    • ANGULAR 4
      • CHAPTER 1. A gentle introduction to ECMASCRIPT 6
      • CHAPTER 2. Diving into TypeScript
      • CHAPTER 3. The wonderful land of Web Components
      • CHAPTER 4. From zero to something
      • CHAPTER 5. The templating syntax
      • CHAPTER 6. Dependency injection
      • CHAPTER 7. Pipes
      • CHAPTER 8. Reactive Programming
      • CHAPTER 9. Building components and directives
      • CHAPTER 10. Styling components and encapsulation
      • CHAPTER 11. Services
      • CHAPTER 12. Testing your app
      • CHAPTER 13. Forms
      • CHAPTER 14. Send and receive data with Http
      • CHAPTER 15. Router
      • CHAPTER 16. Zones and the Angular magic
      • CHAPTER 17. This is the end
    • HTTP 완벽 가이드
      • 게이트웨이 vs 프록시
      • HTTP Header
      • REST API
      • HTTP Method 종류
        • HTTP Status Code
      • HTTP 2.x
  • REFERENCE
    • TECH BLOGS
      • 어썸데브블로그
      • NAVER D2
      • 우아한 형제들
      • 카카오
      • LINE
      • 스포카
      • 티몬
      • NHN
      • 마켓컬리
      • 쿠팡
      • 레진
      • 데일리 호텔
      • 지그재그
      • 스타일쉐어
      • 구글
      • 야놀자
    • ALGORITHM
      • 생활코딩
      • 프로그래머스
      • 백준
      • 알고스팟
      • 코딜리티
      • 구름
      • 릿코드
Powered by GitBook
On this page
  • 티켓 판매 애플리케이션 구현하기
  • 요구사항
  • 클래스 구현
  • 무엇인 문제인가?
  • 예상을 빗나가는 코드
  • 변경에 취약한 코드
  • 설계 개선하기
  • 자율성을 높이자
  • 어떻게 한 것인가
  • 캡슐화와 응집도
  • 절차지향과 객체지향
  • 책임의 이동
  • 더 개선할 수 있다
  • 객제치향 설계

Was this helpful?

  1. STUDY
  2. 오브젝트

1장 객체, 설계

오브젝트의 1장을 요약한 내용 입니다.

Previous오브젝트Next2장 객체지향 프로그래밍

Last updated 3 years ago

Was this helpful?

티켓 판매 애플리케이션 구현하기

요구사항

여러분은 소극장의 홍보도 겸할 겸 관람객들의 발길이 이어지도록 작은 이벤트를 기획하기로 했다. 이벤트의 내용은 간단한데 추첨을 통해 선정된 관람객에게 공연을 무료로 관람할 수 있는 초대장을 발송하는 것이다.

관람객이 가지고 올 수 있는 소지품은 초대장, 현금, 티켓 세 가지뿐이다. 관람객은 소지품을 보관할 용도로 가방을 들고 올 수 있다고 가정하자.

클래스 구현

이제 관람객이 소지품을 보관할 Bag 클래스를 추가하자. 또한 초대장의 보유 여부를 판단하는 hasInvitation 메서드와 티켓의 소유 여부를 판단하는 hasTicket 메서드, 현금을 증가시키거나 감소시키는 plusAmount와 minusAmount 메서드, 초대장을 티켓으로 교환하는 setTicket 메서드를 구현하고 있다.

Bag 인스턴스의 상태는 현금과 초대장을 함께 보관하거나, 초대장 없이 현금만 보관하는 두 가지 중 하나일 것이다.

다음은 관람객이라는 개념을 구현하는 Audience 클래스를 만들 차례다. 관람객은 소지품을 보관하기 위해 가방을 소지할 수 있다.

매표소에는 관람객에게 판매할 티켓과 티켓의 판매 금액이 보관돼 있어야 한다. 매표소를 구현하기 위해 TicketOffice 클래스를 추가할 시간이다. TicketOfifice는 판매하거나 교환해 줄 티켓의 목록(tickets)과 판매 금액(amount)를 인스턴스 변수로 포함한다. 티켓을 판매하는 getTicket 메서드는 편의를 위해 tickets 컬렉션에서 맨 첫 번째 위치에 저장된 Ticket을 반환하는 것으로 구현했다. 또한 판매금액을 더하거나 차감하는 plusAmount와 minusAmount 메서드도 구현돼 있다.

판매원을 구현한 TicketSeller 클래스는 자신이 일하는 매표소(TicketOffice)를 알고 있어야 한다.

https://drive.google.com/uc?id=1aH7b83wUZ2g7jM1is6gM051sM23Mls6A

소극장을 구현하는 클래스는 Theater다. Therater 클래스가 관함객을 맞이할 수 있도록 enter 메서드를 구현하자.

public class Theater {
    private TicketSeller ticketSeller;

    public Theater(TicketSeller ticketSeller) {
        this.ticketSeller = ticketSeller;
    }

    public void enter(Audience audience) {
        if(audience.getBag().hasInvitation()) {
            Ticket ticket = ticketSeller.getTicketOffice().getTicket();
            audience.getBag().getTicket(ticket);
        } else {
            Ticket ticket = ticketSeller.getTicketOffice().getTicket();
            audience.getBag().minusAmount(ticket.getFee());
            ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
            audience.getBag().setTicket(ticket);
        }
    }
}
  • 소극장은 먼저 관람객의 가 안에 초대장이 들어 있는지 확인한다.

  • 만약 초대장이 들어 있다면 이벤트에 당첨된 관람객이므로 판매원에게서 받은 티켓을 관람객의 가방 안에 넣어준다.

  • 가방안에 초대장이 없다면 티켓을 판매해야 한다.

  • 이 경우소극장은 관람객의 가방에서 티켓 금액만큼을 차감한 후 매표소에 금액을 증가시킨다.

  • 마지막으로 소극장은 관람객의 가방 안에 티켓을 넣어줌으로써 관람객의 입장 절차를 끝낸다.

무엇인 문제인가?

로버트 마틴은 <클린 소프트웨어: 애자일 월칙과 패턴, 그리고 실천 방법>에서 소프트웨어 모듈이 가져야 하는 세 가지 기능에 관해 설명한다. 여기서 모듈이란 크기와 상관 없이 클래스나 패키지, 라이브러리와 같이 프로그램을 구성하는 임의의 요소를 의미한다.

모든 소프트웨어 모듈에는 세 가지 목적이 있다.

  • 첫 번째 목적은 실행 중에 제대로 동작하는 것이다. 이것은 모듈의 존재 이유라고 할 수 있다.

  • 두 번째 목적은 변경을 위해 존재하는 것이다. 변경하기 어려운 모듈은 제대로 동작하더라도 개선해야 한다.

  • 세 번째 목적은 코드를 읽는 사람과 의사소통하는 것이다. 모듈은 언제나 개발자가 쉽게 읽고 이할 수 있어야 한다.

마틴에 따르면 모든 모듈은 제대로 실행돼야 하고, 변경이 용이해야 하며, 이해하기 쉬어야 한다.

앞에서 작성한 프로그램은 관람객들을 입장시키는 데 필요한 기능을 오류 없이 정확하게 수행하고 있다. 따라서 제대로 동작해야 한다는 제약은 만족시킨다. 하지만 변경 용이성과 읽는 사람과의 의사소통이라는 목적은 만족시키지 못한다. 지금부터 그 이유를 살펴보자

예상을 빗나가는 코드

문제는 관람객과 판매원이 소극장의 통제를 받는 수동적인 존재하는 점이다.

소극장이 여러분의 허락도 없이 매표소에 보관 중인 티켓과 현금에 마음대로 접근할 수 있기 때문이다. 더 큰 문제는 티켓을 꺼내 관람객의 가방에 집어넣고 관람객에게서 받은 돈을 매표소에 적립하는 일은 여러분이 아닌 소극장이 수행한다는 점이다.

현재의 코드는 우리의 상식과는 다르게 동작하기 때문에 코드를 읽는 사람과 제대로 의사 소통하지 못한다.

코드를 이해하기 어렵게 만드는 또 다른 이유가 있다. 이 코드를 이해하기 위해서는 여러 가지 세부적인 내용들을 한꺼번에 기억하고 있어야 한다는 점이다.

Theater의 enter 메서드를 이해하기 위해서는 Audience가 Bag을 가지고 있고, Bag 안에는 현금과 티켓이 들어 있으며 TicketSeller가 TicketOffice에서 티켓을 판매하고, TicketOffice 안에 돈과 티켓이 보관돼 있다는 모든 사실을 동시에 기억하고 있어야 한다.

하지만 가장 심각한 문제는 이것이 아니다. 그것은 Audience와 TicketSeller를 변경할 경우 Theater도 함께 변경해야 한다는 사실이다.

변경에 취약한 코드

  • 관람객이 가방을 들고 있지 않다면 어떻게 해야 할까?

  • 관람객이 현금이 아니라 신용카드를 이용해서 결제 한다면 어떻게 해야 할까?

  • 판매원이 매표소 밖에서 티켓을 판매해야 한다면 어떻게 해야 할까?

이런 과정이 변경되는 순간 모든 코드가 일시에 흔들리게 된다.

이것은 객체 사이의 의존성과 관련된 문제다. 의존성이라는 말 속에는 어떤 객체가 변경될 때 그 객체에게 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포돼 있다.

그렇다고 해서 객체 사이의 의존성을 완전히 없애는 것이 정답은 아니다. 객체지향 설계는 서로 의존하면서 협력하는 객체들의 공동체를 구축하는 것이다. 따라서 우리의 목표는 애플리케이션의 기능을 구현하는 데 필요한 최소한의 의존성만 유지하고 불필요한 의존성을 제거하는 것이다.

객체 사이의 의존성이 과한 경우를 가리켜 결합도(couping)가 높다고 말한다. 두 객체 사이의 결합도가 높으면 높을수록 함께 변경될 확률도 높아지기 때문에 객체 사이의 결합도를 낮춰 변경이 용이한 설계를 만들어야 한다.

설계 개선하기

코드를 이해하기 어려운 이유는 Theater가 관람객의 가방과 판매원의 매표소에 직접 접근하기 때문이다. 이것은 관람객과 판매원이 자신의 일을 스스로 처리해야 한다는 우리의 직관을 벗어난다. 따라서 Audience와 TicketSeller를 변경할 때 Theater도 함께 변경해야 하기 때문에 전체적으로 코드를 변경하기도 어려워진다.

해결 방법은 간단하다. Theater가 Audience와 TicketSeller에 관해 너무 세세한 부분까지 알지 못하도록 정보를 차단하면 된다. 관람객이 스스로 가방 안의 현금과 초대장을 처리하고 판매원이 스스로 매표소의 티켓과 판매 요금을 다루게 한다면 이 모든 문제를 한 번에 해결할 수 있을 것이다.

자율성을 높이자

Theater의 enter 메서드에서 TicketOffice에 접근하는 모든 코드를 TicketSeller 내부로 숨기는 것이다. TicketSeller에 sellTo 메서드를 추가하고 Thater에 있던 로직을 이 메서드로 옮기자

public class TheaterSeller {
    private TicketOffice ticketOffice;

    public Theater(TicketOffice ticketOffice) {
        this.ticketOffice = ticketOffice;
    }

    public void sellTo(Audience audience) {
        if(audience.getBag().hasInvitation()) {
            Ticket ticket = ticketOffice.getTicket();
            audience.getBag().getTicket(ticket);
        } else {
            Ticket ticket = ticketOffice.getTicket();
            audience.getBag().minusAmount(ticket.getFee());
            ticketOffice.plusAmount(ticket.getFee());
            audience.getBag().setTicket(ticket);
        }
    }
}

public class Theater {
    private TicketSeller ticketSeller;

    public Theater(TicketSeller ticketSeller) {
        this.ticketSeller = ticketSeller;
    }

    public void enter(Audience audience) {
        ticketSeller.sellTo(audience);
    }
}

이처럼 개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것을 캡슐화라고 부른다. 캡슐화를 통해 객체 내부로의 접근을 제한하면 객체와 객체 사이의 결합도를 낮출 수 있기 때문에 설계를 좀 더 쉽게 변경할 수 있게 된다.

Theater는 오직 TicketSeller의 인터페이스에만 의존한다. TicketSeller가 내부에 TicketOffice 인스턴스를 포함하고 있다는 사실은 구현의 영역에 속한다.

TicketSeller 다음으로 Audience의 캡슐화를 개선하자

public class Audience {
    private Bag bag;

    public Audience(Bag bag) {
        this.bag = bag;
    }

    public Long buy(Ticket ticket) {
        if(bag.hasInvitation()) {
            bag.getTicket(ticket);
            return 0;
        } else {
            bag.minusAmount(ticket.getFee());
            bag.setTicket(ticket);
            return ticket.getFee();
        }
    }
}

public class TheaterSeller {
    private TicketOffice ticketOffice;

    public Theater(TicketOffice ticketOffice) {
        this.ticketOffice = ticketOffice;
    }

    public void sellTo(Audience audience) {
        iticketOffice.plusAmount(audience.buy(ticketOffice.getTicket()));
    }
}

수정된 Audience와 TicketSeller는 자신이 가지고 있는 소지품을 스스로 관리한다. 따라서 코드를 읽는 사람과의 의사소통이라는 관점에서 이 코드는 확실히 개선된 것으로 보인다. 더 중요한 점은 Audience나 TicketSeller의 내부 구현을 변경하더라도 Theater를 함께 변경할 필요가 없어졌다는 것이다. 따라서 수정된 코드는 변경 용이성의 측면에서도 확실히 개선됐다고 말할 수 있다.

어떻게 한 것인가

자기 자신의 문제를 스스로 해결하도록 코드를 변경한 것이다. 우리는 우리의 직관을 따랐고 그 결과로 코드는 변경이 용이하고 이해 가능하도록 수정됐다.

캡슐화와 응집도

핵심은 객체 내부의 상태를 캡슐화하고 객체 간에 오직 메시지를 통해서만 상호작용 하도록 만드는 것이다. 밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 객체를 가리켜 응집도가 높다고 말한다. 자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮출 수 있을 뿐더러 응집도를 높일 수 있다.

외부의 간섭을 최대한 배제하고 메시지를 통해서만 협력하는 자율적인 객체들의 공동체를 만드는 것이 훌륭한 객체지향 설계를 얻을 수 있는 지름길인 것이다.

절차지향과 객체지향

변경 전 초기 코드를 다시 살펴보자

Theater의 enter 메서드는 프로세스(Process)이며 Audience, TicketSeller, Bag, TicketOffice는 데이터(Data)이다. 이처럼 프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차적 프로그래밍이라고 부른다.

절차적 프로그래밍은 코드를 읽는 사람과 원활하게 의사소통하지 못한다. 더 큰 문제는 데이터의 변경으로 인한 영향을 지역적으로 고립시키기 어렵다

변경하기 쉬운 설계는 한 번에 하나의 클래스만 변경할 수 있는 설계다.

해결 방법은 자신의 데이터를 스스로 처리하도록 프로세스의 적절한 단계를 Audience와 TicketSeller로 이동시키는 것이다. 이처럼 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍하는 방식을 객체지향 프로그래밍이라고 부른다.

훌륭한 객제치향 설계의 핵심은 캡슐화를 이용해 의존성을 적절히 관리함으로써 객체 사이의 결합도를 낮추는 것이다.

책임의 이동

당신의 코드에서 데이터와 데이터를 사용하는 프로세스가 별도의 객체에 위치하고 있다면 절차적 프로그래밍 방식을 따르고 있을 확률이 높다. 데이터와 데이터를 사용하는 프로세스가 동일한 객체 안에 위치한다면 객체 객체지향 프로그래밍 방식을 따르고 있을 확률이 높다.

설계를 어렵게 만드는 것은 의존성 이라는 것을 기억하라. 해결 방법은 불필요한 의존성을 제거함으로써 객체 사이의 결합도를 낮추는 것이다.

더 개선할 수 있다

Audience는 스스로 티켓을 구매하고 가방안의 내용물을 직접 관리한다. 하지만 Bag은 과거의 Audience처럼 스스로 자기 자신을 책임지지 않고 Audience에 의해 끌려다니는 수동적인 존재다.

Bag을 자율적인 존재로 바꿔보자

public class Bag {
    private Long amount;
    private Ticket ticket;
    private Invitation invitation;

    public Long hold(Ticket ticket) {
        if(hasInvitation()) {
            setTicket(ticket);
            return 0;
        } else {
            setTicket(ticket);
            minusAmount(ticket.getFee());
            return ticket.getFee();
        }
    }

    ...
}

public class Audience {
    public Long buy(Ticket ticket) {
        return bag.hold(ticket);
    }
}

TicketSeller 역시 TicketOffice의 자율권을 침해하였다

public class TicketOffice {
    public void sellTicketTo(Audience audience) {
        plusAmount(audience.buy(getTicket()));
    }
    ...
}

public class TicketSeller {
    public void sellTo(Audience audience) {
        ticketOffice.sellTicketTo(audience));
    }
}

객제치향 설계

설계가 왜 필요한가

설계란 코드를 배치하는 것이다. 설계를 구현과 떨어트려서 이야기하는 것은 불가능하다. 설계는 코드를 작성하는 매 순간 코드를 어떻게 배치할 것인지를 결정하는 과정에서 나온다. 설계는 코드 작성의 일부이며 코드를 작성하지 않고서는 검증할 수 없다.

그렇다면 좋은 설계란 무엇인가?

좋은 설계란 오늘 요구하는 기능을 온전히 수행하면서 내일의 변경을 매끄럽게 수용할 수 있는 설계다

개발을 시작하는 시점에 구현에 필요한 모든 요구사항을 수집하는 것은 불가능에 가깝다. 모든 요구사항을 수집할 수 있다고 가정하더라도 개발이 진행되는 동안 요구사항은 바뀔 수밖에 없다.

요구사항 변경은 필연적으로 코드 수정을 초래하고, 코드 수정은 버그가 발생할 가능성을 높인다. 코드 수정을 회피하려는 가장 큰 원인은 두려움이다. 그리고 그 두려움은 요구사항 변경으로 인해 버그를 추가할지도 모른다는 불확실성에 기인한다.

객체지향 설계

객제치향 프로그래밍은 의존성을 효율적으로 통제할 수 있는 다양한 방법을 제공함으로써 요구사항 변경에 좀 더 수월하게 대응할 수 있는 가능성을 높여준다. 단순히 데이터와 프로세스를 객체라는 덩어리 안으로 밀어 넣었다고 해서 변경하기 쉬운 설계를 얻을 수 있는 것은 아니다.