책에서는 JAVA 로 예제를 진행해서 Typescript 로 변경해 보았다.

import { TicketSeller } from './TicketSeller';
import { Audience } from './Audience';
import { Ticket } from './Ticket';

export class Theater {
  private ticketSeller: TicketSeller;

  constructor(ticketSeller: TicketSeller) {
    this.ticketSeller = ticketSeller;
  }

  public enter(audience: Audience): void {
    if (audience.getBag().hasInvitation()) {
      const ticket: Ticket = this.ticketSeller.getTicketOffice().getTicket();
      audience.getBag().setTicket(ticket);
    } else {
      const ticket: Ticket = this.ticketSeller.getTicketOffice().getTicket();
      audience.getBag().minusAmount(ticket.getFee());
      this.ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
      audience.getBag().setTicket(ticket);
    }
  }
}

수정하기 전 모습의 Theater 클래스. 애플리케이션이 정상 작동은 하지만 많은 문제가 있다고 함. 일단 이 코드를 한글로 풀어보면

소극장은 관람객의 가방을 열어 초대장이 있는지 확인한다. 초대장이 있으면 판매원은 매표소에 있는 티켓을 관람객의 가방 안으로 옮긴다. 초대장이 없다면 티켓 금액만큼의 현금을 관람객의 가방에서 꺼내 매표소에 적립한 후 티켓을 관람객의 가방 안으로 옮긴다.

 

한글로 풀어봐도 되게 어색한 상황이라서 그런지 코드도 많은 문제가 있는 것 같다. 여러가지 문제가 존재하지만 그중에서 가장 심각한 문제는 AudienceTicketSeller 를 변경할 경우 Theater 도 변경해야 한다는 사실이다. 의존성이 매우 높은 코드라고 할 수 있으며, 의존성이 높을수록 유지보수가 힘들어지고 코드를 이해하기 힘들어진다. (Theater 를 통해 모든 작업이 이루어지고 있기 때문!)

위 코드의 문제점을 해결 하는 방법은 높은 의존성을 낮춰주면 되는 것이다.

  • Audience 가 직접 Bag 을 처리할 수 있도록 하기
  • TicketSeller 가 직접 TickketOffice 를 처리할 수 있도록 하기
  • Theater 는 관람객의 입장 여부만 관리할 수 있게 하기
import { TicketSeller } from './TicketSeller';
import { Audience } from './Audience';

export class Theater {
  private ticketSeller: TicketSeller;

  constructor(ticketSeller: TicketSeller) {
    this.ticketSeller = ticketSeller;
  }

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

Theater 는 관람객의 입장에만 관여하도록 코드를 위와 같이 수정한다.

import { TicketOffice } from './TicketOffice';
import { Audience } from './Audience';

export class TicketSeller {
  private ticketOffice: TicketOffice;

  constructor(ticketOffice: TicketOffice) {
    this.ticketOffice = ticketOffice;
  }

  sellTo(audience: Audience) {
    this.ticketOffice.sellTicketTo(audience);
  }
}

TicketSeller 는 외부에서 매표소의 접근을 차단하고 직접 접근할 수 있게 변경.

import { Bag } from './Bag';
import { Ticket } from './Ticket';

export class Audience {
  private bag: Bag;

  constructor(bag: Bag) {
    this.bag = bag;
  }

  buy(ticket: Ticket) {
    return this.bag.hold(ticket);
  }
}

Audience 는 자신의 가방 안에 초대장이 들어 있는지 스스로 확인하며 외부에서 가방을 열어보도록 허용하지 않는다. 외부에서는 Audience 가 가방을 가지고 있는지 없는지 관심을 가질 필요가 없다.

import { Invitation } from './Invitation';
import { Ticket } from './Ticket';

export class Bag {
  private amount: number;
  private invitation: Invitation;
  private ticket: Ticket;

  constructor() {}

  hold(ticket: Ticket): number {
    if (this.hasInvitation()) {
      this.setTicket(ticket);
      return 0;
    }
    this.setTicket(ticket);
    this.minusAmount(ticket.getFee());
    return ticket.getFee();
  }

  private hasInvitation(): boolean {
    return this.invitation !== undefined;
  }

  private hasTicket(): boolean {
    return this.ticket !== undefined;
  }

  private setTicket(ticket: Ticket) {
    this.ticket = ticket;
  }

  private minusAmount(amount: number) {
    this.amount -= amount;
  }

  private plusAmount(amount: number) {
    this.amount += amount;
  }
}

마지막으로 Bag 역시 행위(hold)를 가질 수 있게 수정해 응집도를 높여줄 수 있도록 하자. 응집도란 연관성 없는 작업은 다른 객체에게 위임하고 밀접하게 연관된 작업만을 수행하는 객체를 말한다. hold 를 통해서만 다른 객체와 메시지를 주고받으며 그 외에 자신이 가진 데이터는 자기가 책임지는 것이다.

결론: 설계를 어렵게 만드는 것은 의존성이다. 의존성을 제거해 결합도를 낮추자!

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기