Table of contents
Open Table of contents
티켓 판매 애플리케이션 구현하기
이벤트 요구사항
- 추첨을 통해 선정된 관람객에게 공연을 무료로 관람할 수 있는 초대장을 발송하기
- 이벤트의 당첨된 관람객과 그렇지 못한 관람객을 다른 방식으로 입장시킨다.
무엇이 문제인가
마틴에 따르면 모든 모듈은 실행돼야 하고, 변경이 용이해야 하며, 이해하기 쉬워야 한다. 앞에 프로그램은 변경 용이성과 읽는 사람과의 의사소통이라는 목적은 만족시키지 못한다.
예상을 빗나가는 코드
- 관람객과 판매원이 소극장의 통제를 받는 수동적인 존재라는 점
- 코드를 이해하기 위해서는 여러 가지 세부적인 내용들을 한꺼번에 기억하고 있어야 한다는 점
Audience
와TicketSeller
를 변경할 경우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);
}
}
}
변경에 취약한 코드
- 객체 사이의 의존성은 변경에 대한 영향을 암시한다.
- 애플리케이션을 구현하는데 필요한 최소한의 의존성만 유지하고 불필요한 의존성을 제거해야 한다.
설계 개선하기
자율성을 높이자
1. Theater
의 enter
메서드를 TicketSeller
의 내부로 숨긴다. (캡슐화)
public class TicketSeller {
private TicketOffice ticketOffice;
public TicketSeller(TicketOffice ticketOffice) {
this.ticketOffice = ticketOffice;
}
public void sellTo(Audience audience) {
ticketOffice.plusAmount(audience.buy(ticketOffice.getTicket()));
}
}
Bag
의 접근하는 모든 로직을Audience
내부로 감춘다.
public class Audience {
private Bag bag;
public Audience(Long amount) {
bag = new Bag(amount);
}
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
의 내부 구현을 변경하더라도Theater
는 영향을 받지 않는다.
어떻게 한 것인가
판매자가 티켓을 판매하기 위해 TicketOffice
를 사용하는 모든 부분을 TicketSeller
내부로 옮기고, 관람객이 티켓을 구매하기 위해 Bag
을 사용하는 모든 부분을 Audience
내부로 옮겼다.
=> 자신의 문제를 스스로 해결하도록 코드를 변경한 것이다.
캡슐화와 응집도
- 핵심은 객체 내부의 상태를 캡슐화하고 객체 간에 오직 메시지를 통해서만 상호작용하도록 만드는 것이다.
- 밀접하게 연관된 작업만을 수행하고 연관성이 없는 작업은 다른 객체에게 위임하는 객체를 가리켜 응집도가 높다고 말한다.
절차지향과 객체지향
- 최초에 작성한 코드의 enter 메서드는 프로세스이며 객체들은 데이터다. 이런 방식을 절차지향적 프로그래밍이라고 부른다.
- 절차적 프로그래밍은 우리의 직관에 위배된다.
- 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍 하는 방식을 객체지향 프로그래밍이라고 부른다.
- 객체지향 설계의 핵심은 캡슐화를 이용해 의존성을 적절히 관리함으로써 객체 사이의 결합도를 낮추는 것이다.
책임의 이동
- 객체지향 설계에서는 각 객체가 자신을 스스로 책임진다.
더 개선할 수 있다
Bag
의 내부 상태에 접근하는 모든 로직을Bag
안으로 캡슐화해서 결합도를 낮출 수 있다.
public class Bag {
private Long amount;
private Ticket ticket;
public Bag(Long amount) {
this.amount = amount;
}
public Long hold(Ticket ticket) {
if (hasInvitation()) {
setTicket(ticket);
return 0L;
} else {
setTicket(ticket);
minusAmount(ticket.getFee());
return ticket.getFee();
}
}
private boolean hasInvitation() {
return false;
}
private void setTicket(Ticket ticket) {
this.ticket = ticket;
}
private void minusAmount(Long amount) {
this.amount -= amount;
}
}
그래, 거짓말이다!
- 현실에서는 수동적인 존재라고 하더라도 일단 객체지향의 세계에 들어오면 모든 것이 능동적이고 자율적인 존재로 바뀐다.
- 이처럼 능동적이고 자율적인 존재로 소프트웨어 객체를 설계하는 원칙을 가리켜 의인화(anthropomorphism)라고 부른다.
객체지향 설계
설계가 왜 필요한가
- 좋은 설계란 오늘 요구하는 기능은 온전히 수행하면서 내일의 변경을 매끄럽게 수용할 수 있는 설계다.
- 변경을 수용할 수 있는 설계가 중요한 이유는 요구사항은 항상 변경되고, 코드를 변경할 때 버그를 추가할지도 모른다는 불안감을 해소하기 위해 설계가 필요하다.
객체지향 설계
- 단순히 데이터와 프로세스를 객체라는 덩어리 안으로 밀어 넣었다고 해서 변경하기 쉬운 설계를 얻을 수 있는 것은 아니다.
- 훌륭한 객체지향 설계란 협력하는 객체 사이의 의존성을 적절하게 관리하는 설계다.