본문 바로가기
Book

[독서 기록] 오브젝트 1장 객체, 설계

by Renechoi 2023. 1. 13.
 
오브젝트
객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 존재로 바라보는 것이다. 세번째 걸음을 내디딜 수 있는지 여부는 협력에 참여하는 객체 들에게 얼마나 적절한 역할과 책임을 부여할 수 있느냐에 달려 있다. 객체지향의 마지막 걸음은 앞에서 설명한 개념들을 여러분이 사용하는 프로그래밍 언어라는 틀에 흐트러짐 없이 담아낼 수 있는 기술을 익히는 것이다. 《객체지향의 사실과 오해》가 첫번째 걸음과 두번째 걸음인 객체와 협력에 초점을 맞췄다면 《오브젝트: 코드로 이해하는 객체지향 설계》는 세번째와 네번째 걸음인 책임의 할당과 그 구현에 초점을 맞춘다. 이 책을 읽고 나면 객체에 적절한 역할과 책임을 부여하는 방법과 유연하면서도 요구사항에 적절한 협력을 설계하는 방법을 익히게 될 것이다. 나아가 프로그래밍 언어라는 도구를 이용해 객체지향의 개념과 원칙들을 오롯이 표현할 수 있는 방법 역시 익힐 수 있을 것이다. ★ 이 책에서 다루는 내용 ★ ◎ 역할, 책임, 협력에 기반해 객체지향 프로그램을 설계하고 구현하는 방법 ◎ 응집도와 결합도를 이용해 설계를 트레이드오프하는 방법 ◎ 설계를 유연하게 만드는 다양한 의존성 관리 기법 ◎ 타입 계층을 위한 상속과 코드 재사용을 위한 합성의 개념 ◎ 다양한 설계 원칙과 디자인 패턴
저자
조영호
출판
위키북스
출판일
2019.06.17

 

 

 

오브젝트 1장 객체, 설계 

 


무엇이 문제인가 

 

모든 소프트웨어 모듈에는 세 가지 목적이 있다. 첫 번째 목적은 실행 중에 제대로 동작하는 것이다. 이것은 모듈의 존재 이유라고 할 수 있다. 두 번째 목적은 변경을 위해 존재하는 것이대 다부분의 모듈은 생명주기 동안 변경되기 때문에 간단한 작업만으로도 변경이 가능해야 한다. 변경하기 어려운 모듈은 제대로 동작하더라도 개선해야 한다. 모듈의 세 번째 목적은 코드를 읽는 사람과 의사소통하는 것이다. 모듈은 특별한 훈련 없이도 개발자가 쉽게 이해할 수 있어야 한다. 읽는 사람과 의사소통할 수 없는 모듈은 개선해야 한다. 

- 14p (로버트 마틴의 클린 소프트웨어 중에서 재인용) 

 

앞에서 작성한 프로그램은 관람객들을 입장시키는 데 필요한 기능을 오류 없이 정확하게 수행하고 있다. 따라서 제대로 동작해야 한다는 제약은 만족시킨다. 하지만 불행하게도 변경 용이성과 읽는 사람과의 의사소통이라는 목적은 만족시키지 못한다. 

- 14p 

 

문제는 관람객과 판매원이 소극장의 통제를 받는 수동적인 존재라는 점이다. 

 

 

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

 

- 15p 

 

여러가지 세부적인 내용들을 한꺼번에 기억하고 있어야 한다는 점이다. ... 가장 심각한 문제는 Audience와 TicketSeller를 변경할 경우 Theater도 함께 변경해야 한다는 사실이다. 

 

더 큰 문제는 변경에 취약하다는 것이다. 

- 16p 

 

 

이것은 객체 사이의 의존성과 관련된 문제다. 문제는 의존성이 변경과 관련돼 있다는 점이다. 의존성은 변경에 대한 영향을 암시한다. 의존성이라는 말 속에는 어떤 객체가 변경될 때 그 객체에게 의존하는 다른 객체도 함께 변경될 수 있다는 사실이 내포돼 있다. 

- 16p 

 

관람객과 판매원을 자율적인 존재로 만들기 

 

package ch1;

public class Theater {
    private TicketSeller ticketSeller;

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

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

    
}

 

수정된 Theater 클래스 어디서도 ticketOffice에 접근하지 않는다는 사실에 주목하라. Theater는 ticketOffice가 TicketSeller 내부에 존재한다는 사실을 알지 못한다. Theater는 단지 ticketSeller가 sellTo 메시지를 이해하고 응답할 수 있다는 사실만 알고 있을 뿐이다. 

- 20p 

 

 

package ch1;

public class Audience {
    private Bag bag;

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

    public Bag getBag(){
        return 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는 자신의 가방 안에 초대장이 들어 있는지를 스스로 확인한다. 외부의 제3자가 자신의 가방을 열어보도록 허용하지 않는다. Audience가 Bag을 직접 처리하기 때문에 외부에서는 더 이상 Audience가 Bag을 소유하고 있다는 사실을 알 필요가 없다. 이제 Audience 클래스에서 getBag 메서드를 제거할 수 있고 결과적으로 Bag의 존재를 내부로 캡슐화할 수 있게 됐다. 

 

- 23p 

 

ticketSeller가 Audience의 인터페이스에만 의존하도록 수정 

 

package ch1;

public class TicketSeller {
    private TicketOffice ticketOffice;

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

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


}

- 23p 

 

캡슐화를 개선한 후에 가장 크게 달라진 점은 Audience와 TicketSeller가 내부 구현을 외부에 노출하지 않고 자신의 문제를 스스로 책임지고 해결한다는 것이다.

 

- 24p 

 

 

핵심은 객체 내부의 상태를 캡슐화하고 객체 간에 오직 메시지를 통해서만 상호작용하도록 만드는 것이다. Theater는 TicketSeller의 내부에 대해서는 전혀 알지 못한다. 단지 TicketSeller가 sellTo 메시지를 이해하고 응답할 수 있다는 사실만 알고 있을 뿐이다. TicketSeller 역시 Audience의 내부에 대해서는 전혀 알지 못한다. 단지 Audience가 buy 메시지에 응답할 수 있고 자신이 원하는 결과를 반환할 것이라는 사실만 알고 있을 뿐이다. 

 

밀접하게 연관된 작업만을 수행하고 연관성 없는 작업은 다른 객체에게 위임하는 객체를 가리켜 응집도가 높다고 말한다. 자신의 데이터를 스스로 처리하는 자율적인 객체를 만들면 결합도를 낮출 수 있을뿐더러 응집도를 높일 수 있다. 

 

객체의 응집도를 높이기 위해서는 개체 스스로 자신의 데이터를 책임져야 한다. 자신이 소유하고 있지 않은 데이터를 이용해 작업을 처리하는 객체에게 어떻게 연관성 높은 작업들을 할당할 수 있겠는가? 객체는 자신의 데이터를 스스로 처리하는 자율적인 존재여야 한다. 그것이 객체의 응집도를 높이는 첫걸음이다. 외부의 간섭을 최대한 배제하고 메시지를 통해서만 협력하는 자율적인 객체들의 공동체를 만드는 거이 훌륭한 객체지향 설계를 얻을 수 이는 지름길인 것이다. 

 

- 25 ~ 26p 

 

수정하기 전의 코드에서는 Theater의 enter 메서드 안에서 audience와 TicketSeller로부터 Bag과 TicketOffice를 가져와 관람객을 입장시키는 절차를 구현했다. Audience, TicketSeller, Bag, TicketOffice는 관람객을 입장시키는 데 필요한 정보를 제공하고 모든 처리는 Theater의 enter 메서드 안에 존재했었다는 점에 주목하라. 

 

이 관점에서 Theter의 enter 메서드는 프로세스이며 Audience, TicketSeller, Bag, TicketOffice는 데이터다. 이처러 프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차적 프로그래밍이라고 부른다. 

 

...

 

해결 방법은 자신의 데이터를 스스로 처리하도록 프로세스의 적절한 단계를 Audience와 TicketSeller로 이동시키는 것이다. 수정한 후의 코드에서는 데이터를 사용하는 프로세스가 데이터를 소유하고 있는 Audience와 TicketSeller 내부로 옮겨졌다. 이처럼 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍하는 방식을 객체지향 프로그래밍이라고 부른다. 

 

두 방식의 차이점을 가장 쉽게 이해할 수 있는 방법은 기능을 처리하는 방법을 살펴보는 것이다. .. 작업 흐름이 주로 Theater에 의해 제어된다는 사실을 알 수 있다. 객체지향 세계의 용어를 사용해서 표현하면 책임이 Theater에 집중돼 있는 것이다. 

- 27p 

 

 

변경 후의 객체지향 설계에서는 각 객체가 자신이 맡은 일을 스스로 처리했다. 다시 말해 Theater에 몰려 있던 책임이 개별 객체로 이동한 것이다. 이것이 바로 책임의 이동이 의미하는 것이다. 

 

... 이런 관점에서 객체지향 프로그래밍을 흔히 데이터와 프로세스를 하나의 단위로 통합해 놓는 방식으로 표현하기도 한다. 비록 이 관점이 객체지향을 구현 관점에서만 바라본 지극히 편협한 시각인 것은 맞지만 객체지향에 갓 입문한 사람들에게 어느 정도 도움이 되는 실용적인 조언인 것 또한 사실이다.

 

- 28p 

 

TicketSeller의 책임은 무엇인가? 티켓을 판매하는 것이다. Audience의 책임은 무엇인가? 티켓을 사는 것이다. Theater의 책임은 무엇인가? 관람객을 입장시키는 것이다. 적절한 객체에 적절한 책임을 할당하면 이해하기 쉬운 구조와 읽기 쉬운 코드를 얻게 된다. 

 

- 29p 

 

 

 

적절한 트레이드오프 

 

TicketOffice의 자율성보다는 Audience에 대한 결합도를 낮추는 것이 더 중요하다는 결론에 도달했다. 

- 33p 

 

 

우리는 오늘 완성해야 하는 기능을 구현하는 코드를 짜야 하는 동시에 내일 쉽게 변경할 수 있는 코드를 짜야한다[Metz12]

- 35p 

 

 

반응형