협력의 관점에서 본 객체 지향 설계

협력의 관점에서 본 객체 지향 설계

이전 포스팅(객체 지향 프로그래밍의 진짜 본질은 무엇인가?)에서는 객체 지향의 개념과 좋은 설계의 방향성을 설명했다. 좋은 설계는 변경과 확장에 대응하기 쉬운 구조를 지향하는 것이고, OOP의 핵심은 메시징(Messaging)이라는 것을 알 수 있었다. 본 포스팅에서는 객체 설계에 더 초점을 맞추어 간결하게 정리한다.

객체와 클래스

먼저 객체 지향 시스템을 다시 떠올려본다. 객체 지향 시스템은 하나의 시스템을 개별 객체 간의 협력으로 구조화하는 것이다.

객체가 중요하다

  • 객체 지향 프로그래밍 언어의 속성인 캡슐화, 추상화, 상속, 다형성 혹은 클래스보다 객체가 중요하다.
  • 또한 객체(object) 그 자체보다는 메시지(message)가 중요하다.

객체 지향 시스템 = 객체들의 공동체 혹은 집합

  • 시스템을 객체 간의 협력으로 바라본다.
  • 객체들은 메시지를 통해 협력한다.

시스템을 바라보는 두 관점

  1. 런타임 관점
    • 객체들의 협력: 어플리케이션의 실행 시점에 객체들이 상호작용하는 구조
    • 런타임 관점의 클라이언트는 실제 어플리케이션의 사용자다.
  2. 컴파일 관점
    • 정적인 코드: 객체들의 협력을 수행을 위해, 클래스를 구현하고 연관 관계를 맺는 등의 행위
    • 컴파일 관점에서 클라이언트는 개발자이다.
      • 읽기 쉬운 코드를 작성한다
      • 이해하기 쉬운 구조를 만든다.
      • 변경과 확장에 용이한, 즉, 좋은 품질의 코드를 작성한다.

기능이 설계를 주도한다

  • 사용자에게 좋은 가치를 제공하는 기능 vs 개발자가 읽기 쉽고, 변경하기 쉬운 품질이 좋은 코드
  • 즉, 사용자에게 제공되는 기능을 우선적으로 생각하고, 그 제약 안에서 코드가 설계되어야 한다.
    • 사용자에게 제공되는 기능 = 객체 간 협력
    • 코드의 설계: 클래스의 설계

객체 협력 설계

객체 지향은 ‘객체가 어떻게 협력할 것인가’를 우선적으로 생각해야 한다. 객체가 협력하는 구조의 시스템을 만들기 위해서는 책임 주도 설계를 할 수 있다.

책임 주도 설계(Responsibility Driven Design)

  • 객체를 설계할 때, 가장 중요한 것은 클래스도, 메서드도, 데이터도 아닌 책임이다.
  • 책임 주도 설계를 위해 생각해야 할 것
    • 데이터보다 행동을 먼저 결정한다.
    • 협력의 문맥(Context) 안에서 책임을 결정한다.
  • 책임 주도 설계의 흐름
    1. 시스템이 제공하는 기능으로부터 시스템의 책임을 파악
    2. 시스템의 책임을 더 작은 책임으로 분할
    3. 분할한 책임을 수행할 수 있는 적절한 객체 혹은 역할을 찾아 책임을 할당
      • 어떤 기능을 제공해야 한다 → 어떤 책임이 필요하다
      • 런타임 관점(기능 측면)의 설계를 전제로 한다.
    4. 객체가 책임을 수행하는 중 자율적으로 처리하기 힘든 책임은 다른 객체에게 위임
      • 객체 간의 협력이라는 문맥 내에서 책임을 분할한다.
      • 어떤 객체가 수행하지 못하는 책임은 다른 객체에게 메시지를 보내 요청한다.
      • 또 다른 책임이 생겨나며 또 다른 객체의 설계로 이어질 수도 있다.
      • 이때, 객체의 크기는 작은 것이 좋다(책임의 분할 용이).
    5. 같은 방식으로 객체들의 협력 구조를 만들어간다.

메시지 vs 메서드

  • 메서드(method)와 상태(state)
    • 메서드 = 객체의 행동 = 메시지 처리 방법 = 메시지 수신부 = 작업 수행 코드
    • 상태 = 메서드 구현에 필요한 속성
  • 메시지
    • 메시지 = 다른 객체에게로 요청
    • 하나의 메시지를 서로 다른 여러 개의 메서드가 처리할 수 있다.
    • 메시지 발신자의 입장에서는 그 메시지에 응답 가능한 다른 어떤 객체와도 협력할 수 있다(인터페이스).
    • 메시지와 메서드를 구분하는 것에서 다형성이 시작된다.

절차 지향 vs 객체 지향

절차 지향적 프로그래밍

  • 일반적으로 ‘무엇을 저장할 것인가?’로부터 시작된다.
    • 데이터에 초점: 시스템에서 어떤 데이터들이 필요한가?(e.g. ERD 작성)
  • 이후 자연스럽게 ERD 기반으로 객체를 분리한다.
    • 이때, 데이터를 private 접근 제어자로 은닉하며 getter, setter를 남발하는 객체를 설계하게 된다.
    • 무지성 getter, setter를 사용하는 객체는 아무리 데이터를 은닉했더라도 캡슐화가 된 것이 아니다.
  • 절차적인 코드를 작성한다.
    • 일반적으로 Transaction Script라고 알려져있다.
    • Transaction Script는 엔터프라이즈 애플리케이션 아키텍처 패턴(위키북스, 마틴 파울러)에서 소개되는 코드 패턴으로 하나의 트랜잭션, 비즈니스 로직을 단일 함수 혹은 단일 스크립트에서 처리하는 구조를 갖는다. 이는 절차 지향적인 코드로 분류되고, 자연스럽게 코드 구현과 논리 흐름이 단순하다. 하지만 비즈니스 로직이 복잡해질 수록 코드가 난잡해질 가능성이 높다
    • 하지만 단점만 생각하며 Transaction Script가 무조건적으로 좋지 않다고 생각하지는 말자. 코드가 단순하여 비즈니스 로직 파악이 용이하다는 점, 작은 규모의 개발에서의 생산성 등을 생각하면 장점 또한 명확하다.

객체 지향적 프로그래밍

  • 비즈니스 영역에서 사용되는 객체를 설계한다.
    • 일반적으로 Domain Model이라고 알려져 있다.
    • 이는 객체 지향적으로 설계하는 방법으로 Transaction Script보다 비즈니스 로직을 파악하기에는 어렵지만, 객체 간의 협력 관계를 기반으로 한 시스템 구조를 이해하기 쉽다. 무엇보다 객체 지향의 특징인 확장성, 변경 및 확장 용이성, 재사용성이라는 장점을 갖는다. 책임에 따라 잘 설계된 객체는 응집도가 높다고 표현하며 내부 요소들이 기능적으로 집중되어 있어 수정해야 할 범위가 명확해진다. 또 이미 구축된 도메인 모델은 약간의 수정이 필요할 수도 있지만 비슷한 시스템에 재사용할 수 있다. 마틴 파울러는 도메인 모델 패턴으로 한 번 개발한 커뮤니티 시스템의 도메인 모델을 다른 커뮤니티 시스템 개발에 재사용하는 것을 예시로 든다.
    • Domain Model 패턴은 객체 설계, 데이터베이스 매핑 등 도메인 모델 구축에 어려움이 있기 때문에 단기간의 개발에서는 적합하지 않다. 마찬가지로 무조건적으로 좋다고 생각하지 말자.
  • 이때, 객체 설계는 다음 내용을 고려한다.
    • 객체를 하나의 프로세스, 데이터의 덩어리로 바라본다.
    • 객체의 행동을 먼저 결정하고, 데이터는 나중에 할당한다.
    • 어떤 행동 수행에 필요한 정보 전문가(Information Expert)에게 책임을 할당한다.
      • 책임 주도 설계에 따라 설계하는 방법
      • GRASP Pattern 참고
    • 메시지와 메서드를 분리하여 생각한다.
Author

Jaeyun Cha

Posted on

2022-02-23

Updated on

2023-04-11

Licensed under

댓글