Invitation
when: 공연을 관람할 수 있는 초대일자Ticket
Bag
Bag의 상태는 다음 두 가지다.
→ 생성자를 통해 제약 구현
Audience
TicketOffice
TicketSeller
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);
}
}
}
enter 메서드를 구현Audience가 Bag을 가지고 있고Bag 안에는 현금과 티켓이 있고TicketSeller는 TicketOffice에서 티켓을 판매하고Audience와 TicketSeller를 변경할 경우Theater도 함께 변경해야 함Audience 클래스에서 Bag 제거 (당연히 변경됨)Theater의 enter 메서드 수정 (변경이 당연하지 않음)Theater가 Audience와 TicketSeller에 관해 너무 자세히 알지 못하도록 정보를 차단하면 된다.Theater에서 TicketOffice에 접근하는 모든 코드를 TicketSeller 내부로 숨기기
public class Theater {
private TicketSeller ticketSeller;
public Theater(TicketSeller ticketSeller) {
this.ticketSeller = ticketSeller;
}
public void enter(Audience audience) {
ticketSeller.sellTo(audience);
}
}
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(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에서 TicketOffice에 대한 의존성이 사라졌다.Theater는 TicketSeller가 Audience에게 티켓을 교환해준다는 사실만 알면 된다.객체를 인터페이스와 구현 부로 나누고 인터페이스만 공개하는 것은 객체 사이 결합도를 낮추고 변경하기 쉬운 코드를 작성하기 위해 따라야 하는 기본적인 설계 원칙
TicketSeller에서 Audience에 대한 캡슐화를 개선할 수도 있다.
Audience에 buy 메서드를 추가하고 getBag 메서드에 접근하는 부분을 buy로 옮기면 Bag을 감출 수 있다. public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience audience) {
Long amount = audience.buy(ticketOffice.getTicket());
ticketOffice.plusAmount(amount);
}
}
public class Audience {
private Bag bag;
public Audience(Bag bag) {
this.bag = bag;
}
public Long buy(Ticket ticket) {
if (bag.hasInvitation()) {
bag.setTicket(ticket);
return 0L;
} else {
bag.setTicket(ticket);
bag.minusAmount(ticket.getFee());
return ticket.getFee();
}
}
}
Audience와 TicketSeller가 자신의 소지품을 스스로 관리Audience와 TicketSeller 내부가 바뀌어도 Theater를 변경할 필요가 없다.Theater는 TicketSeller 내부를 전혀 알지 못한다.sellTo 메시지를 이해하고 응답할 수 있다는 사실만 안다.Theater 코드가 절차적이었다.Theater 안에, 입장에 필요한 데이터는 Audience, TicketSeller, Bag 등 다른 곳에 있었다.Theater에 집중되어 있다.
Theater에 의해 제어된다.불필요한 의존성을 제거하여 결합도를 낮춰라. 그러기 위해선 세부사항을 캡슐화해야 한다. 캡슐화를 통해 객체의 자율성과 응집도를 높일 수 있다.
Bag을 자율적인 존재로 만들어보자.
// as-is
public class Audience {
private Bag bag;
public Audience(Bag bag) {
this.bag = bag;
}
public Long buy(Ticket ticket) {
if (bag.hasInvitation()) {
bag.setTicket(ticket);
return 0L;
} else {
bag.setTicket(ticket);
bag.minusAmount(ticket.getFee());
return ticket.getFee();
}
}
}
// to-be
public class Audience {
private Bag bag;
public Audience(Bag bag) {
this.bag = bag;
}
public Long buy(Ticket ticket) {
return bag.hold(ticket);
}
}
public class Bag {
private Long amount;
private Ticket ticket;
private Invitation invitation;
public Long hold(Ticket ticket) {
if (hasInvitation()) { // private method
setTicket(ticket); // private method
return 0L;
} else {
setTicket(ticket);
minusAmount(ticket.getFee()); // private method
return ticket.getFee();
}
}
Bag 내부 상태에 접귾는 모든 로직을 캡슐화hasInvitation, minusAmount, setTicket은 private으로 변경 가능TicketOffice를 자율적인 존재로 만들어보자.
// as-is
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience audience) {
Long amount = audience.buy(ticketOffice.getTicket());
ticketOffice.plusAmount(amount);
}
}
// to-be
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience audience) {
ticketOffice.sellTicketTo(audience);
}
}
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 void sellTicketTo(Audience audience) {
plusAmount(audience.buy(getTicket()));
}
TicketOffice에 원래 없던 Audience에의 의존성이 추가되었다.트레이드오프
설계란 코드를 배치하는 것이다.
- 설계는 코드를 작성하는 매 순간 코드를 어떻게 배치할 것인지를 결정하는 과정
- 좋은 설계란
- 오늘 완성해야 하는 기능을 구현하는 코드이며
- 내일 쉽게 변경할 수 있는 코드
- 즉 기능을 온전히 수행하며 변경이 용이해야 한다.
- 변경 용이성이 중요한 또 다른 이유는 코드 변경 시 버그가 추가될 가능성이 높기 때문이다.
- 코드 수정을 두려워하는 원인은 변경으로 인해 버그를 추가할지도 모른다는 불확실성에서 기인