#01장 객체, 설계

조영호 저 "오브젝트: 코드로 이해하는 객체지향 설계, 2019"를 읽고 정리한 내용입니다.

이책은 객체지향 프로그램을 설계하고 유지보수하는 데 필요한 원칙과 기법을 설명하기 위해 쓰여졌다.

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

  • 소극장을 경영하고 있다고 상상해보자.

  • 이벤트 -> 추첨을 통해 선정된 관람객에게 무료 관람 초대장을 발송하는 것이다.

  • 조건

    • 이벤트에 당첨된 관람객과 그렇지 못한 관람객은 다른 방식으로 입장

      • 당첨된 관람객 -> 초대장을 티켓으로 교환한 후에 입장

      • 당첨되지 않은 관람객 -> 티켓을 구매해야만 입장

  • 상황 - 쉽게 말

    • 관람객은 가방이 있고, 가방에 현금/초대장/티켓이 있다. (티켓 컬럼에 티켓 가격만 있고, 초대장 컬럼에는 초대된날짜만 있다.)

    • 티켓판매자는 오피스에 소속되어 있고, 오피스에는 티켓/현금을 보유하고 있습니다.

조영호, 「오브젝트」, 위키북스, 2019

클래스 생성순서

Invitation -> Ticket -> Bag -> Audience -> TicketOffice -> TicketSeller -> Theater

하지만, 지금 생성한 코드들은 몇가지 문제점을 가지고 있다...

로버트 마틴 소프트웨어 모듈이 가져야 하는 3가지 목적

(모듈이란? 클래스나 패키지, 라이브러리와 같이 프로그램을 구성하는 임의의 요소)

  • 실행 중에 제대로 동작해야 한다. (만족)

  • 변경이 용이해야 한다.

  • 코드를 읽는 사람과 의사소통되어야 한다.

보았던 코드들은 어떤것을 불만족시켰을까요?

Theater 클래스의 enter 메서드 주목.

소극장은 관람객의 가방을 열어, 그 안에 초대장이 들어있는지 살펴본다. 가방 안에 초대장이 들어있으면, 판매원은 매표소에 보관돼 있는 티켓을 관람객의 가방 안으로 옮긴다. 가방 안에 초대장이 들어있지 않다면, 관람객의 가방에서 티켓 금액 만큼 현금을 꺼내 매표소에 적립한 후에 매표소에 보관돼 있는 티켓을 관람객의 가방 안으로 옮긴다.

문제점들

  • 상황적으로.. 제3자 소극장이 맘대로 손님 가방을 열어도 되는걸까?

  • 소극장이 뭔데.. 매표소에 돈을 넣고 티켓을 맘대로 빼가는 걸까? 판매원이 있는데..

  • 코드가 이해하기 어려운 또다른 이유는, Theater의 enter 메서드를 이해하기 위해서는 Audience가 Bag을 가지고 있고, Bag 안에는 현금과 티켓이 들어 있으며 Ticketseller가 Ticketoffice에서 티켓을 판매하고, Ticketoffice 안에 돈과 티켓이 보관돼 있다는 모든 사실을 기억하고 있어야 한다.

가장 심각한 것은, 변경에 취약하다는 것, 관람객과 티켓판매원(Audience, TicketSeller) 클래스를 변경할 경우 Theater도 변경해야 된다. 무슨말?

가정

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

  • 현금이 아니라 신용카드를 이용해서 결제한다면..

  • Bag을 제거해야 할 뿐만 아니라 Audience의 Bag에 직접 접근하는 Theater의 enter 메서드 역시 수정해야 한다.

Theater는, 관람객이 가방을 들고 있고 판매원이 매표소에서만 티켓을 판매한다는, 지나치게 세부적인 사실에 의존해서 동작한다. Audience의 내부에 대해 더 많이 알면 알수록 Audience를 변경하기 어려워진다. (의존성 문제)

그렇다고 객체 사이의 의존성을 완전히 없애는 것이 정답은 아니고, 최소한의 의존성만 유지하고 불필요한 의존성을 제거하는 것이다.

(용어, 결합도coupling이 낮다 = 합리적인 수준으로 서로 의존할 때)

2 설계를 개선해보자.

관계도

조영호, 「오브젝트」, 위키북스, 2019

문제점 분석

  • 이해하기 어려운 이유는 Theater가 관람객의 가방과 판매원의 매표소에 직접 접근하기 때문이다.

  • Theater가 관람객의 가방과 판매원의 매표소에 직접 접근한다는 것은 Theater가 Audience와 Ticketseller에 결합된다는 것을 의미한다.

해결 방법

  • Theater가 Audience와 Ticketseller에 관해 너무 세세한 부분까지 알지 못하도록 정보를 차단하면 된다.

  • Theater가 원하는 것은 관람객이 소극장에 입장하는 것뿐이다. 따라서 관람객이 스스로 가방 안의 현금과 초대장을 처리하고 판매원이 스스로 매표소의 티켓과 판매 요금을 다루게 한다면 모든 문제를 한 번에 해결할 수 있을 것이다.

    • 즉, 관람객과 판매원을 자율적인 존재로 만들면 되는 것이다.

Theater와 TicketSeller 코드 나중에 넣기

  • 외부에서는 TicketOffice에 직접 접근할 수 없다.

  • TicketSeller는 TicketOffice에서 티켓을 꺼내거나 판매 요금을 적립하는 일을 스스로 수행할

    수밖에 없다.

  • 세부적인 사항을 감추는 것을 캡슐화라 한다.

    • 캡슐화 목적: 변경하기 쉬운 객체를 만드는 것

  • Theater는 TicketOffice가 TicketSeller내부에 존재한다는 사실을 알지 둣한다

  • Theater는 오직 TicketSeller의 인터페이스(interface)에만 의존한다. TicketSeller가 내부에

    TicketOffice인스턴스를 포함하고 있다는 사실은 구현(implementation)의 영역에 속한다. 객체를

    인터페이스와 구현으로 나누고 인터페이스만을 공개하는 것은 객체 사이의 결합도를 낮추고 변경하기 쉬운 코드를 작성하기 위해 따라야 하는 가장 기본적인 설계 원칙이다.

  • Ticketseller의 내부 구현이 성공적으로 캡술화

기존 설계도

조영호, 「오브젝트」, 위키북스, 2019

Theater 결합도 낮춘 설계

조영호, 「오브젝트」, 위키북스, 2019

Audience의 캡술화를 개선하자

문제점

  • TicketSeller는 Audience 내부의 Bag 인스턴스에 직접 접근한다.

    • Audience는 여전히 자율적인 존재가 아닌 것이다.

  • buy 메서드는 인자로 전달된 Ticket을 Bag에 넣은 후 지불된 금액을 반환한다.

TicketSeller와 Audience 코드 넣기

Ticketseller가 Audience의 인터페이스에만 의존하도록 수정하자.

TicketSeller와 Audience 코드 넣기

변경후

  • Audience는 자신의 가방 안에 초대장이 들어있는지를 스스로 확인한다.

  • TicketSeller와 Audience 사이의 결합도가 낮아졌다

  • 내부 구현이 캡슐화됐으므로 Audience의 구현을 수정하더라도 Ticketseller에는 영항을 미치지 않는다. 다시 말해, 자율적인 존재가 된 것이다.

무엇이 개선됐는가

  • Audience와 TicketSeller는 자신이 가지고 있는 소지품을 스스로 관리한다.

  • 중요한 점은 Audience나 Ticketseller의 내부 구현을 변경하더라도 Theater를 함께 변경할 필요

    가 없어졌다는 것이다.

  • 변경 용이성의 측면에서도 확실히 개선됐다고 말할 수 있다.

  • 이해하기 쉽고 유연한 설계를 얻을 수 있었다.

3 캡슐화와 응집도

  • Theater는 Ticketseller의 내부에 대해서는 전혀 알지 못한다.

  • Ticketseller 역시 Audience의 내부에 대해서는 전혀 알지 못한다.

  • 자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮출 수 있을뿐더러 응집도를 높일 수 있다.

  • 응집도를 높이기 위해서는 객체 스스로 자신의 데이터를 책임져야 한다.

  • 메시지를 통해서만 협력하는 자율적인 객체들의 공동체를 만드는 것이 흘륭한 객체지향 설계를 얻을 수 있는 지름길인 것이다.

4 절차지향과 객체지향

절차적 프로그래밍의 특징은...

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

  • 첫번째 작성한 코드는 절차적 프로그래밍 방식으로 작성된 코드의 전형적인 의존성 구조를 보여준다. Theater가 모두에 의존하고 있음.

    • 모든 처리가 하나의 클래스 안에 위치하고 나머지 클래스는 단지 데이터의 역할만 수행하기 때문이다.

  • 절차적 프로그래밍의 코드를 읽는 사람과 원활하게 의사소통하지 못한다.

  • 변경은 버그를 부르고 버그에 대한 두려움은 코드를 변경하기 어렵게 만든다. 따라서 절차적 프로그래밍의 세상은 변경하기 어려운 코드를 양산하는 경향이 있다.

변경하기 쉬운 설계, 객체지향이란...

  • 한 번에 하나의 클래스만 변경할 수 있는 설계이고,

  • 두번째 코드 처럼 자신의 데이터를 스스로 처리하도록 프로세스의 적절한 단계를 Audience와 TicketSeller로 이동시키는 것이다.

  • 데이터를 사용하는 프로세스가 데이터를 소유하고 있는 Audience와 Ticketseller 내부로 옮겨졌다. 이처럼 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍하는 방식을 객체지향 프로그래밍(Object-Oriented Programming)이라고 부른다.

  • 하나의 변경으로 인한 여파가 여러 클래스로 전파되는 것을 효율적으로 억제한다

  • 훌륭한 객체지향 설계의 핵심은 캡슐화를 이용해 의존성을 적절히 관리함으로써 객체 사이의 결합도를

    낮추는 것이다.

  • 객체 내부의 변경이 객체 외부에 파급되지 않도록 제어할 수 있기 때문에 변경하기가 수월하다.

5 책임의 이동

  • 위 두방식의 근본적인 차이를 만드는 것은 책임의 이동이다.

  • 아래 1번째 그림 - 책임이 Theater에 집중돼 있다.

    • 결과로 얻게 된 것은 변경에 취약한 설계이다.

  • 아래 2번째 그림 - 필요한 책임이 여러 객체에 걸쳐 분산되어 있다.

    • Theater에 몰려 있던 책임이 개별 객체로 이동한 것이다. 이것이 바로 책임의 이동이 의미하는 것이다.

조영호, 「오브젝트」, 위키북스, 2019
조영호, 「오브젝트」, 위키북스, 2019

  • 객체지향 프로그래밍을 흔히 데이터와 프로세스를 하나의 단위로 통합해 놓는 방식으로 표현하기도 한다. 지극히 편협한 시각인 것은 맞지만 갓 입문한 사람들에게 도움이 되는 실용적인 조언이것은 사실이다.

  • 데이터와 데이터를 사용하는 프로세스가 동일한 객체 안에 위치한다면 객체지향 프로그래밍 방식을 따르고 있을 확률이 높다.

요약하자면, 불필요한 세부사항을 캡슐화하는 자율적인 객체들이 낮은 결합도와 높은 응집도를 가지고 협력하도록 최소한의 의존성만을 남기는 것이 훌륭한 객체지향 설계이다.

6 추가로 개선할 것이 있을까?

앞선 학습들을 이용해 코드를 더 개선해보자.

Audience-Bag 수정

  • 개선 부분 힌트

    • Audience 클래스 Bag에 문제가 있다. Bag을 자율적인 존재로 바꿔보자.

    • Audience를 Bag의 구현이 아닌 인터페이스에만 의존하도록 수정하자.

Audience Bag 코드 넣기

haslnvitation, minusAmount, setTicket 메서드들은 더 이상 외부에서 사용되지 않고 내부에서만 사용되기 때문에 가시성을 private으로 변경했다. (표현력을 높이기 위해 작은 메서드로 계속 사용중..)

TicketSeller-TicketOffice -> 안좋은거

  • TicketSeller역시 TicketOffice의 자율권을 침해한다.

  • sellTicketTo 메서드를 추가하고 Ticketseller의 sellTo 메서드의 내부 코드를 이 메서드로 옮기자.

  • getTicket 메서드와 private으로 변경.

  • 만족스러운가? 안타깝게도 이 변경은 처음에 생각했던 것만큼 만족스럽지 않다.

    • 이유

      • Ticketoffice와 Audience 사이에 의존성이 추가되었다.

      • Ticketoffice가 Audience에게 직접 티켓을 판매하기 때문에 Audience에 관해 알고 있어야 한다.(새로운 의존성이 추가)

      • 의존성의 추가는 높은 결합도를 의미하고, 높은 결합도는 변경하기 어려운 설계를 의미한다.

      • 즉, Ticketoffice의 자율성은 높였지만 전체 설계의 관점에서는 결합도가 상승했다.

조영호, 「오브젝트」, 위키북스, 2019
  • 트레이드오프의 시점이 왔다! 토론 끝에 Ticketoffice의 자율성보다는 Audience에 대한 결합도를 낮추는 것이 더 중요하다는 결론에 도달했다.

  • 예제를 통해 두 가지 사실을 알게 되었을 것

    • 첫째, 어떤 기능을 설계하는 방법은 한 가지 이상일 수 있다.

    • 둘째, 동일한 기능을 한 가지 이상의 방법으로 설계할 수 있기 때문에 결국 설계는 트레이드오프의 산물이다.

  • 설계는 균형의 예술이다.

Last updated