객체지향의 사실과 오해

객체지향의 사실과 오해

불과 몇달전만 해도 내가 "객체지향"과 관련된 글을 쓰게 될 줄은 상상도 못했다.
사실 그 주제에 대해 별로 관심도 없었고, 따로 공부해야될 필요성도 느끼지 못했었다. 여태까지 살아오며 "객체지향"에 대해 한번도 전문적인 교육을 받아본 적은 없었지만, 관련된 블로그 글 몇 개를 읽고 내 나름대로 객체지향으로 개발해보며(이 책을 읽은 후, 객체지향으로 개발했다라고 말하기에는 우습지만) 아 이런게 "객체지향" 이구나 하며 그냥 넘어갔었다.
그러다가 우연히 동기의 추천으로 객체지향의 사실과 오해 라는 책을 읽게 되었다.
마치 이 책은 이런 나같은 사람을 저격(?)하는 듯한 느낌의 책이었다. 한구절 한구절들이 나한테 하는 말 같았고, 너 이러고도 객채지향으로 개발한다고 말할 수 있어? 라며 꾸짖는 것 같았다.
책에는 많은 내용들이 담겨있지만, 그 중에서 인상깊게 읽었던 내용 몇개를 아래에 정리해보았다.

객체지향은 현실 세계를 모방했다?

현실 세계에서 식별가능한 개체 또는 사물은 모두 "상태"와 "행동"을 가진 객체로 볼 수 있고, 이를 소프트웨어 세계에 모방해놓은 것이 "객체지향"이다.

객체지향에 대해 공부하면서 한번쯤은 봤을 법한 문장이다. 이 말이 완전 틀렸다고는 볼 수 없지만, 오류가 분명 존재한다. 현실 세계의 객체와 소프트웨어 세계의 객체 사이에는 분명 차이점이 있다.
예를 들어, "커피"라는 객체가 있다고 하자. 현실 세계의 커피는 스스로 양을 줄이거나 늘릴수도 없는 수동적인 존재이다. 반면 객체지향 세계에서 커피는 스스로 자신의 상태를 관리할 수 있는 자율적인 존재가 된다. 또 필요에 따라 추가적인 능력도 보유할 수 있다.

이처럼 소프트웨어 객체는 일반적으로 현실 객체가 가지지 못한 더많은 특징과 능력을 보유한 객체이다. 객체지향 설계의 목적은 현실 세계를 모방하는 것이 아니라, 새로운 세계를 창조하는 것 이다. 이것을 이해하는 것이 객체지향 설계의 시작이다.

객체지향은 "클래스"가 아니라 "객체"를 지향하는 것이다.

이 책에서도 언급되있지만, 나 또한 객체지향을 생각하면 가장 먼저 "클래스"가 떠오른다.
아마도 우리는 프로그래밍 수업시간 때,

클래스는 객체를 여러개 만들어내는 설계도(또는 틀)이다.

라는 말을 귀에 못박히게 들어왔다. 이 때문에 단순히 객체는 클래스의 생산물이라는 인식이 잡혀있게 된 것 같다.
물론 코딩하는 관점에서는 그렇게 볼 수 있지만, 객체지향이라는 소설의 주인공은 "클래스"가 아니라 "객체"이다. "클래스"의 생산물이 "객체"가 아니라, "객체"를 분류하기 위해 등장한 것이 "클래스"이다.
객체지향 설계에서 중요한 것은 어떤 클래스가 필요한가가 아니라 어떤 객체들이 어떤 메시지를 주고받으며 협력하는가이다. 코드를 담은 클래스의 관점이 아닌 메시지를 주고받는 객체 의 관점으로 애플리케이션을 바라보아야 한다.

객체의 행동이 상태를 결정한다.

만약 지금 학생이라는 객체를 설계해야한다고 가정해보자. 아마도 나는 객체의 필요한 상태(학번, 이름, 나이, 전공 등)부터 정의한 후, 이와 관련되어 필요한 행동이 무엇일까 생각해가며 하나씩 추가할 것이다.
책에서는 이러한 설계 방식의 문제점을 지적하고 있다. 이처럼 상태를 먼저 결정하고, 행동을 후에 결정하는 방식은 협력에 적합한 객체를 만들지 못할 가능성이 크다.
객체의 존재이유는 다른 객체와 협력하기 위해서다. 그리고 이를 달성하기 위해 서로 메시지를 주고 받는다. 따라서 메시지를 처리할 행동이 먼저 정의돼야하고, 그 다음에 행동을 수행하는 데에 필요한 상태가 정의돼야 한다.

메시지가 수신자(객체)의 책임을 결정한다.

앞서 말했었지만, 객채들의 협력에 있어서 가장 중요한 키포인트는 "메시지"이다. 객체들은 자신에게 할당된 책임을 완수하기 위해, 다른 객체들에게 도움을 요청하게 된다. 이를 위해 먼저 수신하기에 적합한 객체를 선택한 후, 해당 객체에게 메세지를 전송하게 된다. 이 때, 수신한 객체는 요청받은 메시지를 반드시 처리해야할 책임을 가지게 된다.
만약 수신한 객체도 다른 객체의 도움이 필요하게 되면, 같은 방식으로 다른 객체에게 메시지를 전송할 것이다. 수신한 그 객체 역시 해당 메시지를 처리할 책임을 가지게 된다. 이런 식으로 서로 간의 주고받는 메시지들을 정의하다되면, 어느새 각 객체들의 책임이 결정될 것이다.
객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택 한다.

메시지는 무엇을(WHAT), 메소드는 어떻게(HOW)

책에는 "메시지""메소드"의 관계가 깔끔하게 정리되어있다.
메시지는 "무엇을(WHAT)" 실행할 것인지를 정의한다. 반면 메소드는 그 메시지를 "어떻게(HOW)" 실행할 것인지를 정의한 것이고, 이 처리방법은 수신자가 전적으로 결정할 수 있다.
따라서 동일한 메시지라 하더라도 서로 다른 방식의 메소드의 의해 처리될 수 있고, 이러한 메시지와 메소드 간의 1:N 관계를 객체지향에서 다형성 이라고 한다.


객체지향 설계 방법

위의 내용을 바탕으로 객체지향 설계 순서를 정리하면,

  1. 도메인에 필요한 객체 정리
  2. 필요한 메시지 추가
  3. 메시지를 수신할 객체 선택
  4. 더이상 필요한 메시지가 없을 때까지 2-3 과정 반복
  5. 객체들이 수신하는 메시지를 바탕으로 객체들의 인터페이스 구성
  6. 객체들의 메소드를 구현

이를 실제 예제에 한번 적용해보자.
예를 들어 "붕어빵 가게"라는 도메인 모델의 객체지향 설계를 한다고 가정해보자.

1. 도메인에 필요한 객체 정리

우선 어떠한 객체가 필요할지 한번 생각해보자. 당연히 "붕어빵"이라는 객체가 있어야될 것이고, "붕어빵"을 구매하는 "손님", 또 "붕어빵"을 만드는 "붕어빵 장수" 객체가 필요할 것이다. 또 "붕어빵 장수"는 "손님"한테 받은 돈을 저장하기 위해 "금고"라는 객체를 하나 가져야할 것이다.
필요한 객체들간의 관계를 정리하여 도메인 모델 로 표현해보면 아래와 같다.

2~4. 필요한 메시지 추가 & 수신할 객체 선택

첫번째로 필요한 메시지는 "붕어빵을 주문하라"이다.

이 메시지를 수신하기에 적합한 객체는 무엇일까? 아마도 "손님"일 것이다.

하지만 "손님" 객체는 붕어빵을 만들 수 없다. 따라서 다른 객체에게 도움을 요청하기 위해 메시지를 날려야 한다. 이 때 등장하는 두번째 메시지는 "붕어빵을 만들어라"이다.

이제 붕어빵을 만들 수 있는 객체를 찾아야한다. 그리고 그 객체는 "붕어빵 장수"일 것이다.

"붕어빵 장수"는 붕어빵을 만들기 전에 먼저 "손님"에게 받은 돈을 저장해야 한다. 따라서 돈을 저장할 수 있는 "금고" 객체에게 메시지를 날리게 된다.

마지막으로 "붕어빵 장수"가 최종적으로 "붕어빵"을 생성하기 위한 메시지가 하나 더 필요하다.

5. 인터페이스 정리하기

객체들이 수신하는 메시지들을 정리하면 아래와 같다.

그리고 이 메시지들은 객체의 인터페이스를 구성한다. 아래는 인터페이스를 java 문법으로 표현한 것이다.

class Customer {  
  public void order(int count) {}
}

class BoongabangMaster {  
  public List<Boongabang> makeBoongabang(int money, int count) {}
}

class Safe {  
  public void saveMoney(int money) {}
}

class Boongabang {  
  // public Boongabang() {} 생략
}

6. 메소드 구현하기

앞서 정리한 인터페이스들을 이제 메소드로 구현해보자. 이 과정에서 객체의 인터페이스가 조금 변경될 수도 있지만, 이는 실제 코드로 구현하면서 나타날 수 있는 충분히 정상적인 현상이다.

class Customer {  
  public void order(int count, BoongabangMaster master) {
    master.makeBoongabang(count, 1000 * count) // 붕어빵 하나의 가격은 1000원
  }
}

class BoongabangMaster {  
  private Safe safe;

  public BoongabangMaster(Safe safe) {
    this.safe = safe;
  }

  public List<Boongabang> makeBoongabang(int count, int money) {
    safe.saveMoney(money);
    List<Boongabang> list = new ArrayList<Boongabang>();
    for(int i=0; i<count; i++) {
      list.add(new Boongabang());
    }
    return list;
  }
}

class Safe {  
  private int money;

  public Safe(int money) {
    this.money = money;
  }

  public void saveMoney(int money) {
    this.money += money;
  }
}

class Boongabang {  
  // public Boongabang() {} 생략
}

출처