public class Invitation {
private LocalDateTime when;
}
package theater;
public class Ticket {
private Long fee;
public Long getFee() {
return fee;
}
}
package theater;
public class Bag {
private Long amount;
private Invitation invitation;
private Ticket ticket;
public Bag(long amount) {
this(null, amount);
}
public Bag(Invitation invitation, long amount) {
this.invitation = invitation;
this.amount = amount;
}
public boolean hasInvitation() {
return invitation != null;
}
public boolean hasTicket() {
return ticket != null;
}
public void setTicket(Ticket ticket) {
this.ticket = ticket;
}
public void minusAmount(Long amount) {
this.amount = this.amount - amount;
}
public void plusAmount(Long amount) {
this.amount = this.amount + amount;
}
}
package theater;
public class Audience {
private Bag bag;
public Audience(Bag bag) {
this.bag = bag;
}
public Bag getBag() {
return bag;
}
}
package theater;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TicketOffice {
private Long amount;
private List<Ticket> tickets = new ArrayList<>();
public TicketOffice(Long amount, Ticket ... tickets) {
this.amount = amount;
this.tickets.addAll(Arrays.asList(tickets));
}
public Ticket getTicket() {
return tickets.remove(0);
}
public void minusAmount(Long amount) {
this.amount -= amount;
}
public void plusAmount(Long amount) {
this.amount += amount;
}
}
package theater;
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public TicketOffice getTicketOffice() {
return ticketOffice;
}
}
package theater;
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().setTicket(ticket);
} else {
Ticket ticket = ticketSeller.getTicketOffice().getTicket();
audience.getBag().minusAmount(ticket.getFee());
ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
audience.getBag().setTicket(ticket);
}
}
}
마지막에 소개한 Theater 클래스의 enter 메서드가 수행하는 일을 풀어 설명하면 다음과 같다.
소극장은 관람객의 가방을 열어 그 안에 초대장을 살펴본다.
가방 안에 초대장이 있으면 판매원은 매표소에 보관돼 있는 티켓을 관람객의 가방으로 옮긴다.
가방 안에 초대장이 들어있지 않으면 관람객의 가방에서 티켓 금액만큼의 현금을 꺼내 매표소에 적립한 후,
매표소에 보관돼 있는 티켓을 관람객의 가방 안으로 옮긴다.
관람객
과 판매원
이 수동적인 존재이다.enter
메서드를 이해하기 위해서는, 아래의 세부사항을 모두 알고 있어야 한다.Audience
가 Bag
을 가지고 있음.Bag
안에는 현금과 티켓이 들어 있음.TicketSeller
가 TicketOffice
에서 티켓을 판매함.TicketOffice
안에 돈과 티켓이 보관되어 있음.Audience
와 TicketSeller
를 변경할 경우 Theater
도 함께 변경해야 한다.Theater
이 Audience
와 TicketSeller
뿐 아니라 내부의 Bag
과 TicketOffice
까지 마음껏 접근할 수 있는것이 문제이다.Audience
가 직접 Bag
을 조작하고, TicketSeller
가 직접 TicketOffice
를 처리하는 자율적인 존재가 되도록 설계를 변경한다.package theater;
public class Theater {
private TicketSeller ticketSeller;
public Theater(TicketSeller ticketSeller) {
this.ticketSeller = ticketSeller;
}
public void enter(Audience audience) {
ticketSeller.sellTo(audience);
}
}
package theater;
public class TicketSeller {d
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);
}
}
}
private
접근제어자의 사용으로ticketSeller
의ticketOffice
의 접근이 제한된다.
ticketSeller
객체 내부에서만 접근 가능하므로,ticketOffice
를 조작하는 일을 스스로 수행할 수 밖에 없어졌다.
Theater
는 단지 ticketSeller
가 sellTo
를 이해하고 응답할 수 있다는 사실만 알고 있다.Theater
는 ticketSeller
의 인터페이스에만 의존한다.ticketSeller
가 내부에 TicketOffice
인스턴스를 포함하고 있다는 사실은 구현영역에 속한다.객체를 인터페이스와 구현으로 나누고, 인터페이스만을 공개하는 것은 객체 사이의 결합도를 낮추기 위한 기본적인 설계 원칙이다.
package theater;
public class Audience {
private Bag bag;
public Audience(Bag bag) {
this.bag = bag;
}
public Long buy(Ticket ticket) {
if (bag.hasInvitation()) {
bag.setTicket(ticket);
} else {
bag.minusAmount(ticket.getFee());
bag.setTicket(ticket);
return ticket.getFee();
}
}
}
package theater;
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience audience) {
ticketOffice.plusAmount(audience.buy(ticketOffice.getTicket()));
}
}
Audience
가 자신의 Bag
을 스스로 관리한다.TicketSeller
는 Audience
의 인터페이스에만 의존한다.Audience
의 내부 구현이 캡술화 되었으므로, 구현이 변경되더라도 TicketSeller
에는 영향을 미치지 않는다.Audience
, TicketSeller
내부만으로 제한되었으므로, 변경 용이성의 측면에서 개선되었다.수정하기 전의 코드
Theater
의enter
메서드에서Audience
와TicketSeller
로부터Bag
과TicketOffice
를 가져와서 관람객을 입장시킨다.
위 코드의 프로세스와 데이터는 아래와 같다.
Theater
의 enter
메서드.Audience
, TicketSeller
, TicketOffice
, Bag
Theater
에서 나머지 모든 클래스에 대한 의존성을 가지고 있다.Theater
에 집중되어 있다.Audience
와 TicketSeller
의 내부 구현을 변경하려면 Theater
의 enter
메서드를 변경해야 한다.Theater
는 오직 TicketSeller
에만 의존한다.TickestSeller
입장에서 Audience
에 대한 의존성이 생겼지만, 적절한 트레이드 오프이다.Theater
에 몰려 있던 책임을 개별 객체로 이동시켰다.TicketOffice
의 자율성을 높이기 위해서는 Audience
의 결합도를 증가시켜야 한다.설계란 코드를 배치하는 것이다.