이번엔 1장 파트 1에서 봤던 Theater 클래스를 개선해보겠다.
앞선 티켓 발급 의존성 문제를 해결하기 위해서 Theater에 속해있던 TicketSeller와 Audience의 접근 동작을 숨긴다.
먼저 TicketSeller에서 문제가 되었던 것은 TicketSeller 내부의 TicketOffice까지 Theater에게 공개되었단 점이다. 이 부분을 제거하기 위해서 Theater의 enter 메서드의 역할을 TicketSeller에게 위임했다.
public void enter(Audience audience) {
if (audience.getBag().hasInvitation()) {
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
audience.getBag().setTicket(ticket);
} else {
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
audience.getBag().minusAmount(ticket.getFee());
ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
audience.getBag().setTicket(ticket);
}
}
변경 전 Theater 클래스
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience audience) {
if (audience.getBag().hasInvitation()) {
Ticket ticket = ticketOffice.getTicket();
audience.getBag().setTicket(ticket);
} else {
Ticket ticket = ticketOffice.getTicket();
audience.getBag().minusAmount(ticket.getFee());
ticketOffice.plusAmount(ticket.getFee());
audience.getBag().setTicket(ticket);
}
}
}
변경 후 TicketSeller 클래스
두 클래스의 차이에 집중하자. Theater
클래스는 TicketSeller
클래스를 통해 TicketOffice
클래스에 접근했었기 때문에 TicketSeller
클래스에게 판매 권한이 위임되며 코드가 상대적으로 간결해졌다.
[변경 전]
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
[변경 후]
Ticket ticket = ticketOffice.getTicket();
결과적으로 TicketOffice에 대한 접근은 TicketSeller만 가능해졌다. 이렇게 객체 내부의 세부 사항을 감추는 것이 캡슐화이다.
수정 후 Theater은 TicketSeller의 내부가 어떻게 구성되었는지 알 수 없게 됐다. sellTo 메서드가 인터페이스로서 TicketSeller 내부 구현 과정을 감췄다.
이 과정은 TicketSeller 내부의 TicketOffice를 캡슐화하는 과정을 나타낸 것이다.
Audience
클래스도 형태에 차이가 있을 뿐 Bag
의 내부 동작을 그대로 드러내고 있다. 이를 막기 위해서 Audience
에서 Bag
을 캡슐화한다.
public Long buy(Ticket ticket) {
if (bag.hasInvitation()) {
bag.setTicket(ticket);
return 0L;
} else {
bag.minusAmount(ticket.getFee());
bag.setTicket(ticket);
return ticket.getFee();
}
}
public void sellTo(Audience audience) {
Ticket ticket = ticketOffice.getTicket();
Long amount = audience.buy(ticket);
ticketOffice.plusAmount(amount);
}
Audience 클래스까지 캡슐화를 수행하게 되면 전체적인 구조는 다음과 같이 Theater 클래스가 비교적 간결한 의존성을 갖도록 변한다.
이렇게 구조를 수정하면 장점이 무엇일까?
만약 티켓 판매 대금을 매표소가 아니라 은행에 저장하고 싶다면? 관람객이 대금을 가방이 아닌 스마트폰으로 결제하고 싶다면?
연관된 클래스에 변경이 일어날 때 더는 Theater 클래스까지 수정할 필요가 없다. 대금을 매표소가 아닌 은행에 저장하고 싶다면 TicketSeller 내부에서 TicketOffice가 아닌 Bank를 사용하면 되고, 대금을 스마트폰으로 결제하고 싶다면 Audience 클래스 내부에서 Bag이 아닌 Phone을 사용하면 된다.
즉, 캡슐화를 수행하게 되면 내부의 수정만 거치면 되기 때문에 변경 용이성 측면에서 개선된다.
객체 내부의 상태를 캡슐화하고 객체 간 오직 메시지를 통해서 상호작용하라.
응집도
밀접하게 연관된 작업만 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 객체르 가리켜 응집도가 높다고 한다.
책에서는 책임의 이동이 가장 중요하다. 데이터가 아닌 책임에 집중하라는 등의 말을 하고 있다. 모두 중요한 이야기이지만 이번 장에서 예제를 통해 알아야 하는 것은 불필요한 의존성을 제거하여 변경 용이성을 높이라는 것이다.
달리 말하면 결합도를 낮춰라. 이번에 결합도를 낮추기 위해서 선택한 방법은 캡슐화였다.
TicketSeller와 Audience를 개선하다보면 이제 나머지 의존성이 눈에 띌 것이다.
Audience 역시 Bag 내부를 마구 헤집고 있다. Bag도 캡슐화를 진행할 수 있다. 이는 TicketOffice도 마찬가지이다. 이런 과정을 수행하기에 앞서, 각 클래스들이 어떤 책임을 수행하는지 짚고 넘어가겠다.
개인적으로 정리해본 각 클래스의 책임이다. 이 책임에 맞도록 한번 더 개선해본다. (사실 관리한다고 책임을 규정하는건 올바르지 못한 것 같다. 관리의 범위가 너무 넓기 때문이다. 당장은 학습을 위해서 넘어간다.)
Bag: 초대장과 티켓, 대금을 관리한다.
public Long hold(Ticket ticket) {
if (hasInvitation()) {
setTicket(ticket);
return 0L;
} else {
minusAmount(ticket.getFee());
setTicket(ticket);
return ticket.getFee();
}
}
Bag 클래스 hold 메서드 (추가)
public Long buy(Ticket ticket) {
return bag.hold(ticket);
}
Audience 클래스 buy 메서드 (수정)
TicketOffice: 티켓 수량과 판매 대금을 관리한다
public void sellTicketTo(Audience audience) {
Ticket ticket = getTicket();
Long amount = audience.buy(ticket);
plusAmount(amount);
}
TicketOffice 클래스 sellTicketTo 메서드 (추가)
public void sellTo(Audience audience) {
ticketOffice.sellTicketTo(audience);
}
TicketSeller 클래스 sellTo 메서드 (수정)
그런데 TicketOffice 클래스 수정은 아래 그림과 같은 새로운 문제를 야기한다. TicketOffice 클래스에 Audience 클래스라는 새로운 의존성이 생겨버렸다.
보기 쉽게 변경 전과 변경 후를 그려봤다. TicketOffice는 Ticket에 대한 책임을 온전히 수행하게 되었지만, Audience라는 새로운 의존성을 갖게 됐다.
이 경우 책에서는 TicketOffice의 자율성보다는 의존성을 없애는 것이 더 좋다고 이야기한다.
이유는 나와있지 않았지만 자율성보다는 결합도를 낮추는 것이 변경 용이성을 높이는데 가깝기 때문이라고 생각한다.
좋은 코드란 무엇인가?
1. 기능이 제대로 동작한다
2. 내일 변경하기 쉽다
변경이 쉬운 코드는 무엇인가?
응집도가 높고 결합도가 낮은 코드이다. (의존성이 낮고 밀접하게 연관된 객체 끼리 상호작용하는 코드)
전체 코드는 아래 링크 확인해주세요
https://github.com/ppparkta/objects/tree/chapter1