객체지향의 사실과 오해

동기

가끔 그런 생각을 한다. 누군가가 그 단어를 물어봤을 때 대답할 수 있는가?라는 생각. 만약 “객체란 무엇인가”라고 내게 물어본다고 했을 때 나는 제대로 답변할 자신이 없었다. 그리고 객체지향 언어인 Java를 배우고 있음에도 불구하고 누군가 객체지향은 무엇일까?라고 내게 물어본다면 나는 대부분의 책에 나와있던 “다형성, 캡슐화, 추상화 등등…“이라고 밖에 말하지 못 할 것같다는 생각이 들었다.

객체에 대한 생각을 바로 잡고 싶었기에 프로그래밍을 잘하는 친구에게 객체에 대한 책 추천을 해달라고 했고 그 친구는 고민없이 이 책을 추천해줬다.

필사

  1. 커피 주문이라는 협력에 참여하는 모든 사람들은 커피가 정확하게 주문되고 주문된 커피가 손님에게 정확하게 전달될 수 있도록 맡은 바 역할과 책임을 다하고 있다는 것이다.
  2. 사람들이 협력을 위해 특정한 역할을 맡고 역할에 적합한 책임을 수행한다는 사실은 몇 가지 중요한 개념을 제시한다.
    1. 여러 사람(객체)이 동일한 역할을 수행할 수 있다. - 손님 입장에서 자신이 주문한 커피를 마실 수만 있다면 어떤 캐시어가 주문을 받는지는 중요하지 않다. 또한 손님은 오늘의 캐시어가 어제의 그 캐시어가 아니어도 크게 개의치 않는다.
    2. 역할은 대체 가능성을 의미한다. - 손님 입장에서 캐시어는 대체 가능하다.
    3. (각 객체는 책임을 수행하는 방법을) 책임을 수행하는 방법은 자율적으로 선택할 수 있다. - 커피 제조를 요청받은 바리스타는 자신만의 독특한 방법으로 커피를 제조할 수 있다.
    4. 한 사람이 동시에 여러 역할을 수행할 수 있다. - 캐시어와 바리스타라는 개별적인 역할을 이용해 협력 관계를 묘사했지만 한 사람이 캐시어와 바리스타의 역할을 동시에 수행하는 것도 가능하다. 따라서 한 사람이 동시에 둘 이상의 역할을 수행하는 것도 가능하다. 회사에 출근하면 사원이라는 역할을 수행하며, 집에 돌아와서는 아이의 부모로서의 역할과 누군가의 남편과 아내라는 역할을 수행한다.
  3. 객체지향 설계의 묘미는 다른 객체와 조화롭게 협력할 수 있을 만큼 충분히 개방적인 동시에 협력에 참여하는 방법을 스스로 결정할 수 있을 만큼 충분히 자율적인 객체들의 공동체를 설계하는 데 있다.
  4. 객체지향의 본질
    1. 객체지향이란 시스템을 상호작용하는 자율적인 객체들의 공동체로 바라보고 객체를 이용해 시스템을 분할하는 방법
    2. 자율적인 객체란 상태와 행위를 함께 지니며 스스로 자기 자신을 책임지는 객체를 의미한다.
    3. 객체는 시스템의 행위를 구현하기 위해 다른 객체와 협력한다. 각 객체는 협력 내에서 정해진 역할을 수행하며 역할은 관련된 책임이다.
    4. 객체는 다른 객체와 협력하기 위해 메세지를 전송하고 메세지를 수신한 객체는 메세지를 처리하는데 적합한 메서드를 자율적으로 선택한다.
  5. 클래스의 구조와 메서드가 아니라 객체의 역할, 책임, 협력에 집중하라. 객체지향은 객체를 지향하는 것이지 클래스를 지향하는 것이 아니다.
  6. 상태를 이용하면 과거에 얽매이지 않고 현재를 기반으로 객체의 행동 방식을 이애할 수 있다. 상태는 근본적으로 세상의 복잡성을 완화하고 인지 과부하를 줄일 수 있는 중요한 개념이다.
  7. 행동은 다른 객체로 하여금 간접적으로 객체의 상태를 변경하는 것을 가능하게 한다. 객체지향의 기본 사상은 상태와 상태를 조작하기 위한 행동을 하나의 단위로 묶는 것이라는 점을 기억하라. 객체는 스스로의 행동에 의해서만 상태가 변경되는 것을 보장함으로써 객체의 자율성을 유지한다.
  8. 객체의 행동은 객체의 상태를 변경시키지만 행동의 결과는 객체의 상태에 의존적이다.
  9. 앨리스 객체의 키를 작게 만드는 것이 앨리스 자신인 것처럼 음료 객체의 양을 줄이는 것은 음료 자신이어야 한다. 따라서 앨리스는 직접적으로 음료의 상태를 변경할 수 없다. 단지 음료에게 자신이 음룔ㄹ 마셨다는 메세지를 전달할 수 있을 뿐이다. 적절한 정도로 음료의 양을 줄이는 것은 메세지를 전달받은 음료 스스로의 몫이다.
  10. 메세지 송신자는 메세지 수신자의 상태 변경에 대해서는 전혀 알지 못 한다.
  11. 상태를 외부에 노출시키지 않고 행동을 경계로 캡슐화하는 것은 결과적으로 객체의 자율성을 높인다. 협력은 유연하고 간결해진다.
  12. 두 객체의 상태가 다르더라도 식별자가 같다면 두 객체를 같은 객체로 판단할 수 있다.
  13. 객체는 상태가 변경될 수 있기 때문에 식별자를 이용한 동일성 검사를 통해 두 인스턴스를 비교할 수 있다.
  14. 일반적으로 객체의 상태를 조회하는 작업을 쿼리라고 하고 객체의 상태를 변경하는 작업을 명령이라고 한다.
  15. 객체는 기계 은유를 사용하면 직관적으로 이해할 수 있다. 사용자가 앞쪽의 앨리스 기계의 ‘음료를 마시다’ 버튼을 눌렀다고 가정하자. 앨리스 기계는 내부적으로 키를 작게 변경한 후 링크를 통해 연결된 음료 기계에게 ‘마셔지다’ 라는 버튼이 눌려지도록 요청을 전송한다. 패러다임 관점에서 이것은 ‘음료를 마시다’라는 메세지를 수신한 앨리스 객체가 메세지를 처리하던 도중 음료 객체에게 ‘마셔지다’라는 메세지를 전송한 것과 같다.
  16. 반드시 기억해야 하는 진실은 바로 이것이다. ‘행동이 상태를 결정한다.’
  17. 객체지향은 현실 세계 모방이 아니라 소프트웨어 객체에 적합한 속성만을 추려내는 것.
  18. 일상적인 체계에서는 어떤 사건이 일어나기 위해 반드시 인간 에이전트가 필요한 반면 객체들은 그들 자신의 체계 안에서 능동적이고 자율적인 에이전트다.
  19. 앨리스는 객체들 중에서 하얀 토끼를 제외한 모든 객체를 ‘트럼프’라는 하나의 개념으로 단순화해서 바라보고 있는 것이다.
  20. 심볼 : 트럼프, 내연 : 몸이 납작하고 두 손과 두 발은 네모 귀퉁이에 달려있는 등장인물
  21. 외연 : 정원사, 병사, 신하, 왕자와 공주, 하객으로 참석한 왕과 왕비들, 하트 잭, 하트 왕과 하트 여왕
  22. 어떤 객체가 어떤 타입에 속하는지를 결정하는 것은 객체가 수행하는 행동이다. 어떤 객체들이 동일한 행동을 수행할 수 있다면 그 객체들은 동일한 타입으로 분류될 수 있다.
  23. 같은 타입에 속한 객체는 행동만 동일하다면 서로 다른 데이터를 가질 수 있다. 여기서 동일한 행동이란 동일한 책임을 의미하며, 동일한 책임이란 동일한 메세지 수신을 의미한다. 따라서 동일한 타입에 속한 객체는 내부의 데이터 표현 방식이 다르더라도 동일한 메세지를 수신하고 이를 처리할 수 있다. 다만 내부의 표현 방식이 다르기 때문에 동일한 메세지를 처리하는 방식은 서로 다를 수 밖에 없다. 이것은 다형성에 의미를 부여한다.
  24. 데이터의 내부 표현 방식과 무관하게 행동만이 고려 대상이라는 사실은 외부에 데이터를 감춰야 한다는 것을 의미한다. 이 원칙을 흔히 캡슐화라고 한다.
  25. 여기서 두 가지 추상화 기법이 함께 사용됐다는 점에 주목하라. 하나는 정원에 있던 등장인물들의 차이점은 배제하고 공통점만 강조함으로써 이들을 공통의 타입인 트럼프 인간으로 분류했다는 것이다. 다른 하나는 트럼프 인간은 좀 더 단순한 관점에서 바라보기 위해 불필요한 특성을 배제하고 좀 더 포괄적인 의미를 지닌 트럼프로 일반화했다는 것이다.
  26. 타입은 추상화다. 타입을 이용하면 객체의 동적인 특성을 추상화할 수 있다. 결국 타입은 시간에 따른 객체의 상태 변경이라는 복잡성을 단순화할 수 있는 효과적인 방법인 것이다.
  27. 객체가 특정 시점에 구체적으로 어떤 상태를 가지느냐를 객체의 스냅샷이라고 한다.
  28. 스냅샷처럼 실제로 객체가 살아움직이는 동안 상태가 어떻게 변하고 어떻게 행동하는지를 포착하는 것을 동적 모델이라고 한다. - 런타임, 디버깅할 때
  29. 객체가 가질 수 있는 모든 상태와 모든 행동을 시간에 독립적으로 표현하는 것. 객체가 속한 타입의 정적인 모델을 표현하기 때문에 정적 모델이라고 한다. - 클래스를 작성할 때
  30. 어떤 협력에 참여하는지가 객체에 필요한 행동을 결정하고, 필요한 행동이 객체의 상태를 결정한다. 개별적인 객체의 행동이나 상태가 아니라 객체들 간의 협력에 집중하라.
  31. 객체의 책임은 ‘객체가 무엇을 알고있는가(knowing)’ 와 ‘무엇을 할 수 있는가(doing)’로 구성된다.
  32. 협력 안에서 객체는 다른 객체로부터 요청이 전송됐을 경우에만 자신에게 주어진 책임을 수행한다. … 두 객체 간의 협력은 메세지를 통해 이뤄진다.
  33. 역할을 이용해 협력을 추상화했기 때문에 ‘판사’나 ‘증인’의 역할을 수행할 수 있는 어떤 객체라도 협력에 참여할 수 있는 것이다. 그렇다면 어떤 객체라도 ‘판사’나 ‘증인’의 역할을 대체할 수 있을까? 물론 그렇지는 않다. 역할을 대체하기 위해서는 각 역할이 수신할 수 있는 메세지를 동일한 방식으로 이해해야 한다. … 따라서 역할을 대체할 수 있는 객체는 동일한 메세지를 이해할 수 있는 객체로 한정된다.
  34. 역할을 이용하면 협력을 추상화함으로써 단순화할 수 있다. 구체적인 객체로 추상적인 역할을 대체해서 동일한 구조의 협력을 다양한 문맥에서 재사용할 수 있는 능력은 과거의 전통적인 패러다임과 구분되는 객체지향만의 힘이다. 그리고 그 힘은 근본적으로 역할의 대체 가능성에서 비롯된다.
  35. 객체가 존재하는 이유는 행위를 수행하며 협력에 참여하기 위해서다. 따라서 실제로 중요한 것은 객체의 행동, 즉 책임이다.
  36. 객체지향 시스템에서 가장 중요한 것은 충분히 자율적인 동시에 충분히 협력적인 객체를 창조하는 것이다.
  37. 객체지향 설계 기법
    1. 책임 주도 설계(Responsibility-Driven Design) : 협력에 필요한 책임들을 식별하고 적합한 객체에게 책임을 할당하는 방식으로 설계 -> 시스템이 수행할 책임을 구현하기 위해 협력 관계를 시작할 적절한 객체를 찾아 시스템의 책임을 객체의 책임으로 할당한다. 객체가 책임을 완수하기 위해 다른 객체의 도움이 필요하다고 판단되면 도움을 요청하기 위해 어떤 메세지가 필요한지 결정한다. 메세지를 결정한 후에는 메세지를 수신하기 위해 적합한 객체를 선택한다. 수신자는 메세지를 처리할 책임이 있다.
    2. 디자인 패턴(design pattern) : 설계 템플릿의 모음. 패턴은 전문가들이 특정 문제를 해결하기 위해 이미 식별해놓은 역할
    3. 테스트 주도 개발(Test-Driven Development) : 실패하는 테스트를 작성하고, 테스트를 통과하는 간단한 코드를 작성한 후, 리펙토링을 한다.
  38. 책임은 협력에 참여하는 의도를 명확하게 설명할 수 있는 수준 안에서 추상적이어야 한다.
  39. 자율적인 책임의 특징은 객체가 ‘어떻게(how)’해야 하는 가가 아니라 ‘무엇(what)’을 해야 하는가를 설명한다는 것이다. ex. 증언할 방법은 모자 장수가 자율적으로 선택할 수 있다.
  40. 모자 장수가 메세지를 처리하기 위해 내부적으로 선택하는 방법을 메소드라고 한다.
  41. 메세지에는 처리 방법과 관련된 어떤 제약도 없기 때문에 동일한 메세지라고 하더라도 서로 다른 방식의 메소드를 이용해 처리할 수 있다. 따라서 다형성을 하나의 메세지와 하나 이상의 메소드 사이의 관계로 볼 수 있다. ex. 앨리스의 이야기에서 모자 장수, 요리사, 앨리스는 모두 왕이 전송한 ‘증언하라’라는 메세지를 이해할 수 있다. 각 수신자는 왕이 전송한 메세지를 처리하기 위해 자신만의 방법을 자유롭게 선택할 수 있다.
  42. 왕(송신자)의 입장에서 ‘증언하라’라는 메세지를 이해할 수 있는 모든 객체(다형적인 수신자)는 동일하다. 또한 왕은 메세지를 수신하는 대상을 알 필요가 없다. ‘증언하라’라는 메세지를 이해하면서 ‘증인’역할을 수행할 수 있는 수신자라면 어떤 누구와도 협력이 가능하다. 따라서 왕에게 영향을 주지 않고도 메세지를 수신하 객체의 타입을 자유롭게 추가할 수 있다.
  43. 기본적으로 다형성은 동일한 역할을 수행할 수 있는 객체들 사이의 대체 가능성을 의미한다.
  44. 객체는 다른 객체의 상태를 묻지 말아야 한다. … 결과적으로 ‘묻지 말고 시켜라’ 스타일은 객체를 자율적으로 만들고 캡슐화를 보장하며 결합도를 낮게 유지시켜 주기 때문에 설계를 유연하게 만든다.
  45. 인터페이스의 특징
    1. 인터페이스의 사용법을 익히기만 하면 내부 구조나 동작 방식을 몰라도 쉽게 대상을 조작하거나 의사를 전달할 수 있다.
    2. 인터페이스 자체는 변경하지 않고 단순히 내부 구성이나 작동 방식만을 변경하는 것은 인터페이스 사용자에게 어떤 영향도 미치지 않는다.
    3. 대상이 변경되더라도 동일한 인터페이스를 제공하기만 하면 아무런 문제 없이 상호작용할 수 있다.
  46. 객체가 협력에 참여하기 위해 수행하는 메세지가 객체의 공용 인터페이스의 모양을 암시한다.
  47. 객체는 상태의 행위를 한데 묶은 후 외부에서 반드시 접근해야만 하는 행위만 골라 공용 인터페이스를 통해 노출한다.
  48. 객체의 내부와 외부를 명확하게 구분하면 설계가 단순해지고 유연하며 변경하기 쉬워진다.
  49. 성공적인 소프트웨어들이 지닌 공통적인 특징은 훌륭한 기능을 제공하는 동시에 사용자가 원하는 새로운 기능을 빠르고 안정적으로 추가할 수 있다는 것이다.
  50. 도메인 모델이란 사용자들이 도메인을 바라보는 관점이다.
  51. 객체지향 패러다임은 사용자의 관점, 설계자의 관점, 코드의 모습을 모두 유사한 형태로 유지할 수 있게 하는 유용한 사고 도구와 프로그래밍 기법을 제공한다.
  52. 소프트웨어 개발의 가장 큰 적은 변경이며 변경은 항상 발생한다는 사실을 기억하라.
  53. 정기예금의 도메인 모델
  54. deposit-domain-model
  55. 이 모델에서 사용하고 있는 개념의 정의와 속성은 은행 업무에서 다뤄지는 정기예금의 정의가 변경되지 않는 한 쉽게 바뀌지 않을 것이다.
  56. 유스케이스의 특성
    1. 유스케이스는 사용자와 시스템 간의 상호작용을 보여주는 ‘텍스트’다.
    2. 유스케이스는 여러 시나리오들의 집합이다. 예를 들어, 이자 계산 유스케이스는 두 가지 시나리오를 가지고 있다. 첫 번째 시나리오는 예금주가 계좌를 선택하고 당일까지의 이자액을 계산하는 것이다. 두 번째 시나리오는 예금주가 계좌를 선택하고 특정일자까지의 이자액을 계산하는 것이다.
    3. 유스케이스는 단순한 단순한 feature 목록과 다르다. 유스케이스의 강점은 단순히 기능을 나열하는 것이 아니라 이야기를 통해 연관된 기능들을 함께 묶을 수 있다는 점이다.
    4. 유스케이스는 사용자 인터페이스와 관련된 세부 정보를 포함하지 말아야 한다. 유스케이스는 자주 변경되는 사용자 인터페이스를 배제하고 사용자 관점에서 시스템의 행위에 초점을 맞춘다.
    5. 유스케이스는 내부 설계와 관련된 정보를 초함하지 않는다.
  57. 객체지향의 가장 큰 장점은 도메인을 모델링하기 위한 기법과 도메인을 프로그래밍하기 위해 사용하는 기법이 동일하다는 점이다. 따라서 도메인 모델링에서 사용한 객체와 개념을 부드럽게 프로그래밍 설계에서의 객체와 클래스로 변환할 수 있다.
  58. 객체지향이 강력한 이유는 연결완전성의 역방향 역시 성립한다는 것이다. 즉, 코드의 변경으로부터 도메인 모델의 변경사항을 유추할 수 있다.
  59. 커피전문점에서 커피를 주문하는 과정을 객체들의 협력 관계로 구현해보자.
    1. 메뉴판, 메뉴 항목, 손님, 바리스타, 커피라는 5가지 객체로 생각할 수 있다.
    2. 손님 객체는 커피를 주문할 책임을 할당받았다.
    3. 고객은 자신이 선택한 메뉴 항목을 누군가가 제공해 줄 것을 요청한다. ‘메뉴 항목을 찾아라’라는 새로운 메세지의 등장이다. 이 경우 메세지에 ‘메뉴 이름’이라는 인자를 포함해 함께 전송한다.
    4. 메뉴판 객체는 그 책임을 처리하고 해당하는 메뉴 항목을 반환한다.
    5. 커피를 제조하라는 요청을 손님이 바리스타에게 보낸다. 그리고 바리스타 객체는 커피를 반환해야 한다.
    6. 바리스타는 커피 객체에게 생성하라는 요청을 보낸다.
  60. 각 객체들이 수신하는 메세지는 객체의 인터페이스를 구성한다.
  61. 설계를 간단히 끝내고 최대한 빨리 구현에 돌입하라. 머릿속에 객체의 협력 구조가 번뜩인다면 그대로 코드를 구현하기 시작하라. 설계가 제대로 그려지지 않는다면 고민하지 말고 실제로 코드를 작성해가면서 협력의 전체적인 밑그림을 그려보라.
  62. 커피전문점 클래스 구조
  63. coffee-class-structure
  64. 실제로 훌륭한 설계를 결정하는 측면은 명세 관점인 객체의 인터페이스다.
  65. 타입을 객체의 분류 장치로 적용할 수 있으려면 다음과 같은 세 가지 관점에서의 정의가 필요하다.
    1. 심볼 : 타입을 가리키는 간략한 이름이나 명칭
    2. 내연 : 타입의 완전한 정의. 내연의 의미를 이용해 객체가 타입에 속하는 지 여부를 확인할 수 있다.
    3. 외연 : 타입에 속하는 모든 객체들의 집합
  66. 어떤 타입이 다른 타입보다 일반적이라면 이 타입을 슈퍼타입이라고 한다. 어떤 타입이 다른 타입보다 좀 더 특수하다면 이 타입을 서브타입이라고 한다.
  67. 예를 들어 고양이는 육식동물의 특수화이고 육식동물은 포유류의 특수화이기 때문에 고양이는 육식동물과 포유류가 가진 모든 본질적인 속성을 포함한다.
  68. 프로그래밍 언어를 이용해 일반화와 특수화 관계를 구현하는 가장 일반적인 방법은 클래스 간의 상속을 사용하는 것이다.
  69. 서브타입은 슈퍼 타입이 가지고 있는 속성과 연관관계 면에서 100% 일치해야 한다. 따라서 서브타입이 슈퍼 타입을 대체하더라도 구조에 관한 동일한 기대집합을 만족시킬 수 있다.
  70. 추상화 매커니즘(230 p)
  71. abstract-mechanism

후기

예시가 너무나 적절히 들어있어서 이해하기 쉬웠다. 다만 아예 쌩 초보인 사람들에게는 생각보다 어려운 부분이 있을 것 같기도 했다. 나는 객체를 데이터로 보고 코드를 짰었는데 그 방식이 잘못된 것이라고 알려주며 방향또한 제대로 잡아줬기에 배우는 것이 많았다. 나는 책을 사지 않고 도서관에 최대한 빌리는 편인데 이 책은 두고두고 보기에도 좋을 것 같아 새 책으로 살 계획이다. 살까말까 고민하는 사람이 있다면 그냥 따지지 말고 사라고 할만큼 추천하고 싶다.