Today
Total
07-03 00:02
관리 메뉴

T-coding

[오브젝트] 1장 객체, 설계 본문

Book/오브젝트

[오브젝트] 1장 객체, 설계

Tcoding 2023. 2. 28. 16:13
728x90
728x90

조영호 님의 오브젝트라는 책을 읽으며 내용을 정리하는 글입니다.

조영호 - 오브젝트

 

 

 

01) 티켓 판매 애플리케이션 구현하기

 

소극장을 운영하는데, 이벤트를 통해 추첨을 통해 선정된 관람객에게 무료로 관람할 수 있는 초대장을 발송하였다.

이때, 이벤트에 당첨된 관람객은 초대장을 티켓으로 교환 후 입장 가능하고 이벤트에 당첨되지 않은 관람객은 티켓 구매 후에 입장 가능하다.

 

그림 1

1. 소극장은 먼저 관람객의 가방 안에 초대장이 들어 있는지 확인한다.

2. 초대장이 들어 있다면 티켓을 관람객의 가방 안에 넣어주고, 초대장이 없다면 티켓을 판매해야 한다.

3. 소극장은 관람객의 가방에서 티켓 금액만큼을 차감한 후 매표소에 금액을 증가시킨다.

4. 관람객의 가방 안에 티켓을 넣어주고 입장 절차를 끝낸다.

 

 

02) 문제점

 

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. 소극장이라 초대장을 확인하기 위해 관람객의 가방을 마음대로 열어본다.

2. 소극장이 매표소에 보관 중인 티켓과 현금에 마음대로 접근한다.

 

이는 우리의 예상에서 크게 벗어나서 동작하는 코드이다.

또한 코드를 이해하기 위해서 여러 세부적인 내용들을 기억하고 있어야 한다.

 

가장 심각한 문제는 Audience와 TicketSeller를 변경할 경우 Theater도 함께 변경돼야 한다는 점이다.

 

관람객이 가방을 들고 있다는 가정이 바뀌었다고 하자.

Audience 클래스에서 Bag을 제거해야 할 뿐만 아니라 AudienceBag에 직접 접근하는 Theaterenter 메서드도 수정해야 한다.

이처럼 다른 클래스가 Audience 내부에 대해 더 많이 알면 알수록 Audience를 변경하기 어려워진다.

 

이것은 객체 사이의 의존성(dependency)과 관련된 문제다.

의존성이라는 말속에는 어떤 객체가 변경될 때 그 객체에게 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포돼 있다.

 

위 다이어그램은 너무 많은 클래스에 의존한다.

객체 사이의 의존성이 과한 경우를 가리켜 결합도(coupling)가 높다고 한다.

우리의 목표는 애플리케이션의 기능을 구현하는 데 필요한 최소한의 의존성만 유지하고 불필요한 의존성을 제거하는 것이다.

 

 

03) 설계 개선하기

 

현재 코드를 이해하기 어려운 이유는 Theater가 Audience 가방과 TicketSeller의 매표소에 직접 접근하기 때문이다.

이는 Theater가 Audience와 TicketSeller에 결합된다는 것을 의미한다.

 

이를 해결하기 위해 Theater가 Audience와 TicketSeller에 관해 너무 세세한 부분까지 알지 못하도록 정보를 차단하면 된다.

관람객이 스스로 가방 안의 현금과 초대장을 처리하고 판매원이 스스로 매표소의 티켓과 판매 요금을 다루게 한다면 해결할 수 있다.

다시 말해서 관람객과 판매원을 자율적인 존재로 만들면 되는 것이다.

 

1. Theater의 enter 메서드에서 TicketOffice에 접근하는 모든 코드를 TicketSeller내부로 숨긴다.

TicketSeller에 sellTo 메서드를 추가하고 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 = 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);
        }
    }
}

이제 ticketOffice에 대한 접근은 오직 TicketSeller 안에만 존재하게 된다.

TicketSeller는 ticketOffice에서 티켓을 꺼내 판매 요금을 적립하는 일을 스스로 수행할 수밖에 없다.

이처럼 개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것을 캡슐화(encapsulation)라고 한다.

 

 

이제 Theater의 enter 메서드는 sellTo 메서드를 호출하는 간단한 코드로 바뀐다.

public class Theater {
    private TicketSeller ticketSeller;

    public Theater(TicketSeller ticketSeller) {
        this.ticketSeller = ticketSeller;
    }

    public void enter(Audience audience) {
        ticketSeller.sellTo(audience);
    }
}

수정된 Theater는 ticketOffice가 TicketSeller 내부에 존재한다는 사실을 알지 못한다.

Theater는 오직 TicketSeller의 인터페이스(interface)에만 의존한다.

 

TicketSeller가 내부에 TicketOffice 인스턴스를 포함하고 있다는 사실은 구현(implementation)의 영역에 속한다.

객체를 인터페이스와 구현으로 나누고 인터페이스만을 공개하여 객체 사이의 결합도를 낮추고 변경하기 위한 코드로 작성하였다.

 

 

2. Audience를 자율적인 존재로 만들기

Audience에 buy 메서드를 추가하고 TicketSeller의 sellTo 메서드에서 getBag 메서드에 접근하는 부분을 이 메서드로 옮기자!

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는 자신의 가방 안에 초대장이 들어있는지를 스스로 확인한다.

Audience가 Bag을 직접 처리하기 때문에 외부에서는 더 이상 Audience가 Bag을 소유하고 있다는 사실을 알 필요가 없다.

-> 캡슐화 성공!

 

 

이제 TicketSeller의 sellTo 메서드는 buy 메서드를 호출하는 간단한 코드로 바뀐다.

public class TicketSeller {
    private TicketOffice ticketOffice;

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

    public void sellTo(Audience audience) {
        ticketOffice.plusAmount(audience.buy(ticketOffice.getTicket()));
    }
}

TicketSeller와 Audience 사이의 결합도가 낮아졌다.

내부 구현이 캡슐화 됐으므로 Audience의 구현을 수정하더라도 TicketSeller에는 영향을 미치지 않는다.

 

 

무엇이 개선됐는가?

수정된 Audience와 TicketSeller는 자신이 가지고 있는 소지품을 스스로 관리한다.

이는 코드를 읽는 사람과의 의사소통이라는 관점에서 개선되었다.

 

Audience나 TicketSeller 내부 구현을 변경하더라도 Theater를 함께 변경할 필요가 없어졌다.

변경 용이성의 측면에서도 확실히 개선되었다.

 

 

개선한 방법

Audience가 직접 내용물을 확인하고, 추가하고, 제거하는 작업을 스스로 하였다.

TicketSeller 역시 매표소에 보관된 티켓을 직접 판매하도록 바꾸었다.

객체의 자율성을 높이는 방향으로 설계를 개선했다!

 

 

캡슐화와 응집도

객체 내부의 상태를 캡슐화하고 객체 간에 오직 메시지를 통해서 상호작용하도록 만드는 것

Theater는 TicketSeller의 내부에 대해 알지 못하고, TicketSeller 역시 Audience의 내부에 대해 전혀 알지 못한다.

 

밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에 위임하는 객체를 응집도(cohesion)가 높다고 말한다.

자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮추고 응집도를 높일 수 있다.

 

 

절차지향과 객체지향

- 절차지향

이 관점에서 Theater의 enter 메서드는 프로세스(Process)이고, Audience, TicketSeller, Bag, TicketOffice는 데이터(Data)다.

프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차지향적 프로그래밍(Procedural Programming)이라고 부른다.

 

- 객체지향

자신의 데이터를 스스로 처리하도록 프로세스의 적절한 단계를 Audience와 TicketSeller로 이동시켰다.

데이터와 프로세스가 동일한 모듈 내부에 위치시키는 방식을 객체지향 프로그래밍(Object-Oriented Programming)이라고 부른다.

 

 

04) 객체지향 설계

 

변경 가능한 코드란 이해하기 쉬운 코드다.

훌륭한 객체지향 설계란 협력하는 객체 사이의 의존성을 적절하게 관리하는 설계다.

 

협력하는 객체들 사이의 의존성을 적절하게 관리하여 변경에 용이한 설계를 만들어보자!

 

 

 

728x90
300x250