오브젝트

[오브젝트] 객체의 책임 분리 1

chadongmin 2024. 2. 8. 14:53

1.  티켓 판매 애플리케이션

요구사항 : 

티켓판매 애플리케이션의 요구사항은 다음과 같다.

  1. 이벤트에 당첨된 사람들에게는 초대장을 발송한다.
  2. 입장할때 초대장이 있는 사람들은 초대장을 티켓을 교환한다.
  3. 초대장이 없는 사람들에게는 티켓을 판매한다

2. 클래스 구조

클래스 다이어 그램은 다음과 같다.

 

Audience는 Bag을 필드로 가지고 있고, Bag은 필드로 Invitation, Ticket을 가지고 있다.

TicketSeller는 TicketOffice를 필드로 가지고 있고, TicketOffice는 Ticket을 필드로 가지고 있다.

 

관객을 입장시키기 위한 Theater의 enter 메서드를 보자

public class Theater {

    private TicketSeller ticketSeller;

    public Theater(TicketSeller ticketSeller) {

        this.ticketSeller = ticketSeller;
    }

    /**
     * 문제점 :
     * Theater 클래스가 관객의 가방에 초대장이 있는지 없는지를 확인하고 있음.
     * 클래스마다 책임이 분산되어 있지 않기 떄문에, 유연하게 확장할 수 없다.
     */

    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().plusAmount(ticket.getFee());
            ticketSeller.getTicketOffice().minusAmount(ticket.getFee());
            audience.getBag().setTicket(ticket);
        }
    }
}

 

3. 문제점

1. Theater가 관객을 입장시키는데 관객의 가방을 직접 열어서 초대장이 있는지 확인한다.

2. 초대장이 있으면 Theater가 직접 관객의 가방에 티켓을 넣는다.

3. 초대장이 없으면 극장이 직접 티켓셀러를 호출하여 관객에게 티켓을 팔고 티켓 오피스에 잔고를 업데이트한다.

 

즉, 책임이 분산되어 있지 않고 극장 클래스에 모든 책임이 집중되어 있다.

 

4. 개선

package ObjectStudy.reservation;

public class TicketSeller {
    private TicketOffice ticketOffice;

    public TicketSeller(TicketOffice ticketOffice) {
        this.ticketOffice = ticketOffice;
    }

//    public TicketOffice getTicketOffice() {
//        return ticketOffice;
//    }

    public void sellTicket(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.minusAmount(ticket.getFee());
            audience.getBag().setTicket(ticket);
        }
    }
}

 

TicketSeller 클래스에 SellTicket 메서드를 추가한다. Theater 클래스의 enter 메소드를 모두 옮긴다.

그러면 기존처럼 Theater 클래스가 Ticket ticket = ticketSeller.getTicketOffice().getTicket(); 와 같이 Ticket인스턴스에 접근할 필요가 없다.

TicketSeller에게 관객의 가방에 초대장이 있는지 없는지 확인하고 티켓을 팔지 말지 결정하는 권한을 위임한 것이다.

즉, Theater는 TicketOffice의 존재를 몰라도 되는 것이다.

 

하지만 여기에도 아직 문제점이 있다.

 

Audience 즉 관객이 수동적으로 TicketSeller에 의해 자신의 가방에 초대장이 있는지 없는지 강제로 검사당하는데도 아무것도 하지 못한다.

 

public class Audience {

    private Bag bag;

    public Audience(Bag bag) {
        this.bag = bag;
    }

    public Bag getBag() {
        return bag;
    }

    public Long buyTicket(Ticket ticket){
        if (bag.hasInvitation()){
            bag.setTicket(ticket);
            return 0L;
        }else{
            bag.minusAmount(ticket.getFee());
            bag.setTicket(ticket);
            return ticket.getFee();
        }
    }
}

Audience 클래스에 buyTicket이라는 메서드를 추가하여 직접 관객이 자신의 가방에 초대장이 있는지 없는지를 확인한다.

없으면 가방에 있는 돈에서 티켓값만큼을 반환하도록 구현한다.

 

public class TicketSeller {
    private TicketOffice ticketOffice;

    public TicketSeller(TicketOffice ticketOffice) {
        this.ticketOffice = ticketOffice;
    }

    public void sellTicket(Audience audience){
        ticketOffice.plusAmount(audience.buyTicket(ticketOffice.getTicket()));
    }
}

 

그러면 TicketSeller의 sellTicket 메서드는 Audience가 반환한 돈을 전달받아 TicketOffice에 업데이트 시키기만 하면 되는 것이다.

즉 Audience에게 자신의 가방에서 초대장이 있는지 없는지 확인하고 없다면 티켓가격을 반환할 책임을 위임한 것이다.

 


 

즉 기존의 Theater이 모든 로직에 참여하던 중앙집중화된 방식이 아닌,

 

Theater는 TicketSeller의 SellTicket 메서드를 호출한다.
그러면 TicketSeller는 Audience의 BuyTicket 메서드를 호출한다.
Audience는 자신의 가방에 초대장이 있는지 없는지 확인하고 없으면 티켓 가격을 반환한다.

즉 관객은 자신의 가방에 초대장이 있는지 없는지 확인하고 없다면 티켓가격만을 반환할 책임이 있다.
티켓셀러는 관객이 반환한 티켓가격을 티켓오피스의 잔고를 업데이트할 책임만이 있다.

극장은 티켓셀러의 sellTicket 메소드를 호출하여, 티켓을 팔라고만 지시했다. 
어떻게 파는지는 극장의 관심사가 아니다. 

티켓셀러의 입장에서는 관객이 Bag을 들고있는지 Wallet을 들고있는지 관심이 없다.
티켓오피스의 금고에 잔액을 업데이트 하기 위해 초대장이 있으면 0원(무료)이고, 초대장이 없으면 자신에게 티켓가격을 지불하기만 하면 된다.

즉 이렇게 책임을 분산시키는 것이 변화와 확장에 유연한 결합도는 낮고 응집도는 높은 코드를 작성하는 것이다.