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
에의 의존성이 추가되었다.트레이드오프
설계란 코드를 배치하는 것이다.
- 설계는 코드를 작성하는 매 순간 코드를 어떻게 배치할 것인지를 결정하는 과정
- 좋은 설계란
- 오늘 완성해야 하는 기능을 구현하는 코드이며
- 내일 쉽게 변경할 수 있는 코드
- 즉 기능을 온전히 수행하며 변경이 용이해야 한다.
- 변경 용이성이 중요한 또 다른 이유는 코드 변경 시 버그가 추가될 가능성이 높기 때문이다.
- 코드 수정을 두려워하는 원인은 변경으로 인해 버그를 추가할지도 모른다는 불확실성에서 기인