본문 바로가기
생각 정리/독서

2024-03. 오브젝트 (1)

by Jiyoon-park 2024. 2. 8.

아니 벌써~ 24년도의 세번째 책! 오브젝트: 코드로 이해하는 객체지향 설계

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=193681076

 

오브젝트

역할, 책임, 협력에 기반해 객체지향 프로그램을 설계하고 구현하는 방법, 응집도와 결합도를 이용해 설계를 트레이드오프하는 방법, 설계를 유연하게 만드는 다양한 의존성 관리 기법, 타입 계

www.aladin.co.kr

첨언하자면, 이 책을 읽기 전에 객체지향의 사실과 오해를 읽는 게 좋다고 들었다~ 그리고 실제로 이해가 수월한 듯~!

 

'설계에 관해 설명할 때 가장 유용한 도구는 코드 그 자체다' 라는 글래스의 주장대로

오브젝트는 객체지향에 대한 설명을 위해 (주로) 자바 코드가 가득이다.

이걸 타입스크립트로 변환해가면서 내용을 정리해보려 한다~

 

chap01. 객체, 설계

영화관에서 초대권을 관객들에게 뿌렸다면,

영화관에 입장할 수 있는 방법은 2가지-초대권을 티켓으로 교환받거나/돈으로 티켓을 구매하거나 이다.

 

더 넓은 흐름 속 많은 협력이 있겠지만, 영화관직원-관객 사이의 협력에 집중해 들여다보자.

영화관직원은 티켓을 판다. 관객이 초대권이 있다면 티켓을 주고, 초대권이 없다면 티켓 금액을 받고 티켓을 준다.

위의 요구사항을 코드로 풀어보자면 아래와 같다.

 
 // 영화관직원
 class TicketSeller {
   constructor(ticketOffice: TicketOffice) {
     this.ticketOffice = ticketOffice;
   }

   sellTo(audience: Audience) {
     if (audience.getBag().hasInvitation()) {                         // 초대권이 있다면
       const ticket = this.ticketOffice.getTicket();
       audience.getBag().setTicket(ticket);                          // 티켓을 준다   
     } else {                                                                              // 초대권이 없다면
       const ticket = this.ticketOffice.getTicket();
       audience.getBag().minusAmount(ticket.getFee());
       this.ticketOffice.plusAmount(ticket.getFee());         // 티켓 금액을 받고
       audience.getBag().setTicket(ticket);                         // 티켓을 준다
     }
   }
 }
 
 // 관객
 class Audience {
   constructor(bag: Bag) {
     this.bag = bag;
   }

   getBag() {
     return this.bag;
   }
 }
 

 

 

위 코드는 요구사항을 만족한다. 하지만 좋은 코드일까? 만약 관객이 가방이 아니라 지갑에서 교환권이나 티켓을 꺼낸다면 어떻게 될까?

우리는 Audience 내부의 상태값을 wallet으로 변경하고, getBag을 getWallet으로 변경할 것이다. 그리고 이 변경은 TicketSeller 내부의 sellTo 까지도 전파되어 TicketSeller의 변경까지 피치못할 것이다.

 

위 코드는 변경에 취약하다. TicketSeller는 Audience의 내부 데이터를 마음껏 들여다보며 관객의 돈을 꺼내기도 하고, 관객의 가방에 티켓을 넣어주기도 한다. Audience는 TicketSeller 없이 아무것도 하지 못하면서, 결국 두 객체는 아주 높은 의존성과 결합도를 가지게 된다.

 

 

 

그럼 이 두 객체의 사이 의존성을 끊어내기 위해서 해야할 건 무엇일까? 우선은 Audience가 TicketSeller에게 의존하지 않고 자율적으로 본인 내부의 데이터는 본인이 관리하도록 해야한다.

 

관객이 스스로 티켓을 받는다면 어떨까? 관객 스스로 초대권을 확인하고, 초대권이 있다면 티켓을 받아 가방에 넣고 티켓이 없다면, 돈을 내고 티켓을 받아 가방에 넣도록 buy 로직을 Audience 객체 안으로 넣어주자. 

 
 // 영화관직원
 class TicketSeller {
   constructor(ticketOffice: TicketOffice) {
     this.ticketOffice = ticketOffice;
   }

   sellTo(audience: Audience) {
     const ticket = this.ticketOffice.getTicket();
     const amount = audience.buy(ticket);
     this.ticketOffice.plusAmount(amount);
   }
 }

 // 관객
 class Audience {
   constructor(bag: Bag) {
     this.bag = bag;
   }

   buy(ticket: Ticket) {
     if (this.bag.hasInvitation()) {
       this.bag.setTicket(ticket);
       return 0;
     } else {
       this.bag.minusAmount(ticket.getFee());
       this.bag.setTicket(ticket);
       return thicket.getFee();
     }
   }
 }

 

자, 이제 다시 만약 관객이 가방이 아니라 지갑에서 교환권이나 티켓을 꺼낸다면 이라는 변경 사항을 반영한다고 했을 때 어떤가.

우리는 Audience 내부의 상태값을 wallet으로 변경하고, getBag을 getWallet으로 변경할 것이다. 그리고 TicketSeller도 변경을 해줘야할까? 그렇지 않다. 이제 TicketSeller는 Audience의 buy라는 메시지만 알면 된다. 티켓을 구매함에 있어 Audience가 가방에서 돈을 꺼내든, 지갑에서 돈을 꺼내든, 돈으로 구매를 하든, 신용카드로 구매를 하든 전혀 상관이 없다. 

 

Audience의 내부 상태가 캡슐화 되고 TicketSeller와 메시지를 통해서 상호작용하면서, Audience는 자율성을 얻었다. 티켓을 구매하는 책임만 다한다면, 내부적으로 어떻게 구매하든 상관이 없어지면서 변경에 유연하게 대응할 수 있게 되었다!

 

만약 관객이 가방이 아니라 지갑에서 교환권이나 티켓을 꺼낸다면 처럼 요구사항은 언제든지 변경될 수 있다. 우리는 현재의 요구사항을 만족하면서도, 미래에 바뀔 요구사항을 쉽게 받아들일 수 잇는 유연한 코드를 작성해야한다.

책에서 나온 좋은 설계에 대한 정의가 인상깊어 기록한다.

 

좋은 설계란 오늘 요구하는 기능을 온전히 수행하면서 내일의 변경을 매끄럽게 수용할 수 있는 설계이다. 변경을 수용할 수 있는 설계가 중요한 이유는 요구사항이 항상 변경되기 때문이다. 개발을 시작하는 시점에 구현에 필요한 모든 요구사항을 수집하는 것은 불가능에 가깝다. 모든 요구사항을 수집할 수 있다고 가정하더라도 개발이 잔행되는 동안 요구사항은 바뀔 수 밖에 없다. (...) 따라서 우리가 진정으로 원하는 것은 변경에 유연하게 대응할 수 있는 코드이다. -p.35

'생각 정리 > 독서' 카테고리의 다른 글

2024-04. Clean Code  (0) 2024.09.02
2024-02. 객체지향의 사실과 오해  (2) 2024.02.02
2024-01. 소프트웨어 장인  (2) 2024.01.17
부자 아빠, 가난한 아빠  (0) 2022.09.06
위대한 나의 발견, 강점 혁명  (0) 2022.07.04