티스토리 뷰

객체지향 패러다임 관점에서의 핵심

  • 역할: 객체들이 애플리케이션의 기능구현을 위해 수행하는 상호 작용.
  • 책임: 객체가 협력에 참여하기 위해 수행하는 로직.
  • 협력: 수행하는 책임들이 모여 객체가 수행하는 구성.

협력

객체 지향에서의 객체는 고립된 존재가 아닌 애플리케이션 기능을 수행하기 위해 다른 객체와 협력하는 사회적 존재.

public class Screening {
    private final Movie movie;
    
    public Reservation reserve(Customer customer, int audienceCount) {
        return new Reservation(customer, this, calculateFee(audienceCount), audienceCount);
    }

    private Money calculateFee(int audienceCount) {
        // movie에게 요금 계산을 위임함.
        return this.movie.calculateMovieFee(this).times(audienceCount);
    }
}
public class Movie {
    /** * 영화 관람 금액 */
    private final Money fee;
    /** * 영화 할인 정책 정보 */
    private DiscountPolicy discountPolicy;
    
    public Money calculateMovieFee(Screening screening) {
        return this.fee.minus(this.discountPolicy.calculateDiscountAmount(screening));
    }
}

영화 티켓팅 예제에서 Screening이 Movie에게 요금계산을 위임하는 이유는 기본 요금(fee)과 할인 정책(discountPolicy)을 잘 알고 있기 때문이다.
만약, Screening에서 요금계산을 한다면 Movie의 인스턴스 변수에 접근해야 하므로 캡슐화가 깨지면서 Movie의자율성이 훼손된다.

자율성이 보장되려면 필요한 정보와 정보에 기반한 행동들을 같이 객체안에 모아야한다. (캡슐화)

자율적인 객체는 자신의 책임을 수행하던중에 필요한 정보가 없거나 외부의 도움이 필요하면 적절한 객체에 메시지를 전송해 협력을 요청한다.

협력이 설계의 문맥을 결정한다.

애플리케이션에 어떤 객체가 필요하다면 그 이유는 해당 객체가 협력에 참여하고있기 때문이며, 협력에 참여할 수 있는 이유는 협력에 필요한 적절한 행동을 보유하고있기 떄문이다.

객체의 행동을 결정하는 것은 객체가 참여하고 있는 협력.
협력은 객체가 필요한 이유와 객체가 수행하는 동기를 제공한다.

영화 티켓팅 예제에서 Movie객체는 일반적으로 생각하는 영화 재생에 대한 행동은 존재하지 않는다. 이는 Movie가 영화를 예매하기 위한 협력에 참여하고있고 요금을 계산하는 책임을 지고있기 떄문이다.

협력이라는 문맥을 고려하지않고 행동을 결정하는 것은 의미가 없다. 협력이 존재하기 때문에 객체가 존재하는것이다.

객체의 행동을 결정하는 것은 협력이며, 상태를 결정하는 것은 행동이다.


책임

협력에 참여하기 위해 객체가 수행하는 행동들을 의미한다.

객체에 의해 정의되는 응집도 있는 행위의 집합으로 유지해야 하는 정보와 수행하는 행동에대해 개략적으로 서술한 문장이다.

책임은 객체가 수행할 수 있는 행동을 종합적으로 간략하게 서술하기떄문에 메시지보다 추상적이고 개념적으로도 더 크다.

객체지향에서 제일 중요한 부분은 책임이며 애플리케이션 전체의 품질을 결정한다.

책임 할당

자율적인 객체를 만드는 기본적인 방법은 책임을 수행하는데 필요한 정보를 가장 잘 알고있는 전문가에게 그 책임을 할당하는 것.

영화 티켓팅 예제를 통한 시스템 설계

설계의 출발점은 사용자에게 제공하는 기능을 하나의 책임으로 본다.
시스템 전체의 책임을 완료하는데 필요한 더 작은 책임을 할당해 나가며 모양을 갖춰나간다.

영화 티켓팅 예제의 책임은 영화를 예매하는 것으로 출발한다.

 

영화 티켓팅 전체 기능

 

영화 예매를 위해서는 영화 정보, 상영 회차, 시간을 알아야하며 해당 정보의 전문가는 Screening이다.

 

예매의 정보 전문가

 

public class Screening {
    /** * 상영될 영화 정보 */
    private final Movie movie;
    /** * 상영 회차 정보 */
    private final int sequence;
    /** * 상영 시작 시간 */
    private final LocalDateTime whenScreened;
    
    private Money calculateFee(int audienceCount) {
        return this.movie.calculateMovieFee(this).times(audienceCount);
    }
}

영화 예매를 위해서 가겨을 계산해야하나 Screening은 가격 계산에 대한 정보가 부족하므로 다른 객체에 요청해야한다.

 

가격 계산 위임

 

가격을 계산하기 위해서는 할인 정책 및 기본 가격을 알고있는 Movie가 정보전문가 이므로 Movie에게 할당한다.

 

Moive에게 가격 계산 위임

 

public class Movie {
	/** * 영화 관람 금액 */
    private final Money fee;
    /** * 영화 할인 정책 정보 */
    private DiscountPolicy discountPolicy;
    
    public Money calculateMovieFee(Screening screening) {
        return this.fee.minus(this.discountPolicy.calculateDiscountAmount(screening));
    }
}

위와 같이 설계는 협력에 필요한 메시지를 찾고 적절한 객체를 선택하는 반복적 과정으로 이뤄진다.
협력을 설계하면서 객체의 책임을 식별하며 얻은 최종 결과물은 시스템을 구성하는 객체들의 인터페이스 및 오퍼레이션 목록이된다.

책임 주도 설계

책임을 갖고 책임을 수행할 적절한 객체를 찾아 할당하는 방식으로 협력을 설계하는 것을 책임주도설계라고 한다.

  1. 시스템이 사용자에게 제공해야할 기능인 시스템 전체 책임을 파악.
  2. 시스템 전체의 책임을 더 작은 책임으로 분할.
  3. 분할된 책임을 수행할 적절한 객체를 선택.
  4. 객체의 책임 수행도중 다른 객체의 도움이 필요할 경우 이를 책임질 적절한 객체를 선택 및 할당.
  5. 두 객체가 협력하게 함.

메시지가 객체를 결정한다.

책임을 할당하는데 먼저 메시지를 식별하고 처리할 객체를 나중에 선택했다는게 중요!

객체가 최소한의 인터페이스를 가질 수 있게된다.

  • 필요한 메시지가 식별 될때까지 어떠한것도 추가하지 않기 때문이다.

객체는 추상적인 인터페이스를 가질수 있게된다.

  • 객체의 인터페이스는 무엇(What)을 표현하지만 어떻게(How, 구체적 내용)은 포함하지 않는다.
  • 메시지는 외부 객체가 요청하는 무언가(What)의미하기 때문에 무엇을 수행할지에 대한 초점을 맞출수 있다.

행동이 상태를 결정한다.

객체가 존재하는 이유는 협력에 참여하기 위해서이며 따라서 객체는 협력에 필요한 행동을 제공해야한다.

사람들이 가장 쉽게 빠지는 실수는 객체의 행동이 아닌 상태에 초점을 맞춰 개발하며 이런방식은 내부구현이 노출 되도록 설계되어 캡슐화에 저해된다. (데이터 주도 설계는 지양한다.)

캡슐화 위반을 하지 않도록 구현은 뒤로 미루며 객체의 행위를 고려하기 위해서 항상 협력이라는 문맥을 염두한다.
협력이 객체의 행동을 결정하고, 행동이 상태를 결정한다. 그리고 이 행동이 객체의 책임이 된다.


역할

특정한 협력에서 수행하는 책임의 집합을 역할이라고 한다.

역할이 중요한 이유는 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수있다.

객체에만 집중한다면 영화 티켓팅 예제의 가격 할인 금액을 계산할때와 금액 기준 할인금액과 비율 기준 할인금액 계산 두 객체가 있으므로 아래와 같이 두 부분으로 나뉘게된다.

 

객체 중심 설계

 

위와 같이 구현하면 대부분 중복코드가 발생할 것이다.

객체가 아닌 책임에 초점을 맞춰야한다.

AmountDiscountPolicy와 PercentDiscountPolicy모두 할인 금액이라는 동일한 책임(역할)을 가지고 있으므로 하나로 통합 할 수 있다.

 

 

동일한 책임을 수행하는 역할을 기반으로 두개의 협력을 하나로 통합할 수 있다.
(내가 이해하기로는 책임은 구체적 행동이고 역할은 추상적인 개념을 의미하는거 같다.)

 

역할과 책임의 관계

 

객체와 역할

역할은 객체가 참여할수 있는 일종의 슬롯이다.
협력에 책임을 수행하는 대상이 한 종류라면 객체, 여러 종류라면 역할이라고 부른다.

다양한 객체들이 협력에 참여하는것이 확실하면 역할로 시작하나 결정내리기 어렵다면 구체적인 객체로 시작하여 탐색, 단순화, 통합하다 보면 자연스럽게 역할이 나타난다.
(처음부터 추상화를 진행하지 말고 구체적 객체로 시작하다가 개발 진행하면서 필요시에 추상화를 도입하라는 의미인거 같다.)


느낀점

뭔가 이번장은 정리하기 어려웠다.

개념적인 내용들이기도 하고 내가 제대로 이해한건지 글을쓰면서 몇번씩 읽어봐도 알면서도 모르겠는 내용들이 있어서인거 같다.

이번장의 키워드는 협력, 책임, 역할 그리고 행동인거 같다.

객체의 존재이유는 협력에서 나타나며 객체의 행동을 결정할때는 느낌이나 추측이 아닌 협력을 기준으로 객체의 행동을 결정해야한다.

책임을 짊어질 객체는 정보의 전문가에게 할당해야하며 이를 구현시에 캡슐화가 무너지지 않도록 자율적인 객체가 될수 있게 주의한다.
(여기서 자율적인 객체란 내부 상태값을 외부에 표출하여 외부에 의해 수동적으로 움직이는 객체가 아닌 자신의 책임을 명확하게 지고 있으며 외부의 의존도가 낮은 객체를 의미하는 듯 싶다.)

내가 이해하는바가 맞는지 모르겠지만 설계시 불분명할때 처음엔 구체적 책임을 지닌 객체로 개발을 진행하다가 책임의 중복이 발생한다면 중복된 책임을 하나의 역할로 뽑아내어 슬롯처럼 여러 객체의 책임을 바꿔가며 쓸수있게 만들어야 한다.

내가 이해한게 맞다면 여기서의 역할은 추상적인 의미(abstract, interface)이며 책임은 구체적인 객체의 행동(class -> method)로 이해하면 될거 같다.

이번장을 읽으면서 제일 기억에 남는것은 협력이 객체의 행동을 만들고 객체의 행동이 객체의 상태를 결정한다 이다.

지금까지 개발하면서 협력은 고사하고 일단 객체의 상태부터 정의 후에 기계적으로 getter/setter를 만들어 구현하였으며 이로인해 캡슐화가 깨져 데이터 중심의 개발을 진행하고있었음에 반성하였다.

앞으로 개발시에 이를 좀더 고민하고 생각하여 개발을 진행해야겠다.

 

댓글