[스터디] 오브젝트 – Chapter1: 객체, 설계

티켓 판매 어플리케이션

Chapter1에서 간단하게 티켓 판매 어플리케이션 구현을 해본다. 그리고 객체지향 설계가 안 된 코드에서 어떤 문제가 생기는지 파악하고 문제를 개선한다.

티켓 판매 어플리케이션은 Invitation, Ticket, Bag, Audience, TicketOffice, TicketSeller, Theater 클래스로 구성된다. 각 클래스들은 역할이 존재한다. 대표적으로 Theater 클래스는 관람객을 입장시키는 역할을 가지고 있다. 이 역할은 enter 메서드에 구현되어 있다.

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);
        }
    }
}

극장에 관람객이 들어왔을 때, 극장은 관람객의 가방에 초대장이 있는지 확인한다. 초대장이 있다면 매표소에서 티켓을 가져와서 관람객의 가방에 티켓을 넣어준다. 초대장이 없다면 관람객의 가방에 있는 돈을 차감시키고 매표소의 잔고에 차감한 돈을 더해준다. 그리고 관람객의 가방에 티켓을 넣어준다.

예제로 나온 티켓 판매 어플리케이션이 동작하는 방식을 간단하게 설명했다. 이 예제는 우리의 예상대로 동작한다. 하지만 객체지향 관점에서 본다면 몇 가지 문제점을 발견할 수 있다.

문제점 파악하기

책에서는 로버트 마틴의 책에 나오는 ‘소프트웨어 모듈이 가져야 할 세 가지 기능’을 이야기 한다.

소프트웨어 모듈의 세 가지 목적

  1. 실행 중에 제대로 동작하기
  2. 변경에 용이하게 만들기
  3. 코드를 읽는 사람이 쉽게 이해하게 만들기

우리가 앞에서 작성한 코드는 관람객을 입장시키는 기능을 정확히 수행한다. 따라서 1번 목적을 만족시킨다. 하지만 변경 용이성과 이해하기 쉽게 만드는 목적은 만족시키지 못한다. 책에서는 이 두 가지의 문제점을 파악하고 코드를 개선한다.

예상을 빗나가는 코드

위에서 Theater 클래스의 enter 메서드의 동작 방식을 설명했다. 극장은 관람객의 가방, 극장 매표소에 직접 접근해서 각 객체의 동작을 제어한다. 책에서는 이 지점을 문제점으로 꼽는다.

우리가 관람객이라고 생각해보자. 예제 코드처럼 극장이 관람객의 가방에서 초대장이 있는지 마음대로 확인하고, 허락없이 가방 안에 있는 돈을 가져가서 티켓을 구매하면 어떻게 될까? 왜 내 가방을 마음대로 뒤적이고 돈까지 알아서 가져가지? 라는 생각이 들 것이다.

현실에서는 관람객이 매표소 직원에게 초대장을 건네서 티켓으로 교환한다. 또는 가방에서 돈을 꺼내 티켓을 구매한다. 매표소 직원은 매표소에 있는 티켓을 꺼내 관람객에게 건네고 관람객에게 돈을 받아 매표소에 보관한다. 하지만 예제 코드에서 관람객, 매표소 직원은 그렇게 동작하지 않는다. 현재 코드는 우리의 상식과는 너무나도 다르게 동작하기 때문에 코드를 읽는 사람과 제대로 의사소통하지 못한다.

변경에 취약한 코드

이 예제 코드의 더 큰 문제는 변경에 취약하다는 것이다.

만약 관람객이 가방을 들고 있다는 가정이 바뀌면 어떻게 될까? Audience 클래스에서 Bag을 제거하고, AudienceBag에 직접 접근하는 Theaterenter 메소드 또한 수정해야 한다.

책에서는 이것을 객체 사이의 의존성과 관련된 문제라고 한다. 의존성이라는 말 속에는 어떤 객체가 변경될 때 그 객체에 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포되어 있다고 한다.

설계 개선하기

이 코드는 우리의 예상대로 코드가 흘러가지 않아서 이해햐기 어렵고, 의존성이 과해서 변경하기 어렵다.

코드를 이해하기 어려운 이유는 Theater가 관람객의 가방과 매표소 직원에 직접 접근하기 때문이다. 이것은 관람객과 매표소 직원이 자신의 일을 스스로 처리해야 한다는 우리의 직관을 벗어난다.

해결 방법은 간단하다. Theater가 관람객과 매표소 직원을 통제하는 코드가 아니라, 자율적으로 행동하는 존재로 만들면 된다.

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) {
        ticketOffice.plusAmount(audience.buy(ticketOffice.getTicket()));
    }
}
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();
        }
    }
}

개선된 부분

수정된 AudienceTicketSeller는 자신이 갖고있는 객체들을 스스로 관리한다. 이것은 우리의 예상대로 코드가 흘러가기 때문에 코드를 읽는 사람이 이해하기 더 쉬워졌다.

더 중요한 부분은 AudienceTicketSeller의 내부 구현을 변경해도 Theater를 함께 변경할 필요가 없어졌다. 이제 Audience의 동작을 수정하고 싶다면 Audience 내부 구현만 신경쓰면 된다. 따라서 수정된 코드는 변경의 용이성 측면에서도 개선이 되었다.

캡슐화와 응집도

코드 개선의 핵심은 객체 내부의 상태를 캡슐화하고 객체간에 오직 메시지를 통해서만 상호작용 하도록 만드는 것이다. TheaterTicketSeller의 내부는 알지 못한다. 단지 TicketSellersellTo 메시지를 이해하고 응답할 수 있다는 사실만 알고 있을 뿐이다. TicketSeller 역시 Audience의 내부는 알지 못하고, Audiencebuy 메시지에 응답할 수 있다는 사실만 알고 있을 뿐이다. 이처럼 자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮추고, 응집도를 높일 수 있다.

객체지향 설계

우리가 만드는 프로그램은 두 가지 요구사항을 만족시켜야 한다. 첫 번째는 기능을 구현해야하고, 두 번째는 변하는 요구사항에 대해 쉽게 변경이 가능해야 한다. 그러므로 좋은 설계란 오늘 요구하는 기능을 온전히 수행하면서 내일의 변경을 매끄럽게 수용할 수 있는 설계다.

객체지향 프로그래밍은 의존성을 효율적으로 통제할 수 있는 방법을 제공하기 때문에 변경에 좀 더 수월하게 대응할 수 있는 가능성을 높여준다.

Leave a Comment