이 글은 객체지향의 사실과 오해를 기반으로 작성했다. 자세한 내용은 책을 직접 읽어보는 것을 추천한다.
오늘은 객체지향에 대한 오해들과 객체지향에서 핵심을 이루는 개념들을 간단하게 알아볼 것이다.
사람들은 보통 객체지향 프로그래밍에 대해 쉽게 설명하기 위해 "실세계를 직접적이고 직관적으로 모델링할 수 있는 패러다임" 이라고 말한다. 이 패러다임은 현실 속에 존재하는 사물을 최대한 유사하게 모방해 소프트웨어 내부로 옮겨온다. 즉, 객체지향 소프트웨어는 실세계의 투영이며, 객체란 현실 세계에 존재하는 사물에 대한 추상화가 된다.
이런 사고 방식은 처음에 객체지향을 학습하기에는 좋다. 객체지향의 기반을 이루는 철학적인 개념을 설명하기에 딱이기 때문이다. 하지만 객체지향의 유연하고 실용적인 관점까지 설명하기에는 부족하다. 그리고 객체지향의 목표는 실세계를 모방하는 것이 아니라 새로운 세계를 창조하는 것이다. 이것이 객체지향에 대한 가장 큰 오해이다.
위에서 언급한 패러다임이 객체지향을 어느 정도 이해하는데 도움을 주는 것이 사실이다. 그리고 우리는 객체지향은 이 패러다임에서 말하는 역할이 전부가 아니라는 사실을 알게 되었다. 문제점을 자각한 상태가 된 것이다! 이제 객체지향에 친숙하게 접근하기 위해 "실세계의 모방" 이라는 전통적인 방법으로 알아보자.
개발자들의 생명수인 커피를 마시기 위해 카페에 가면, 바로 객체지향의 철학을 마주치게 된다.
카페에는 커피를 주문하는 손님, 주문을 받는 캐시어, 커피를 제조하는 바리스타가 있고 이 사이에는 암묵적인 협력 관계가 존재한다. 손님이 주문한 커피를 받아 마실 수 있는 이유는 협력하는 존재들이 역할이 있고, 협력하는 과정에서 각각의 역할에 맞게 책임 을 수행하기 때문이다. 손님은 마시고 싶은 커피를 주문할 수 있는 책임, 캐시어는 주문을 받는 책임, 바리스타는 주문된 커피를 제조하는 책임을 수행한다.
"역할과 책임이 다른게 뭐야?" 라고 생각할 수도 있다. 역할은 책임을 내포하는 개념이다. 바리스타는 커피를 만드는 책임이 있다. 더불어 커피를 만드는데 사용한 식기를 설거지하는 책임도 있을 수 있다.
조금 더 깊게 들어가면, 협력을 위해 특정한 역할을 맡고 적합한 책임을 수행한다는 개념에서는 중요한 개념을 제시한다.
여러 사람이 동일한 역할을 수행할 수 있다.
역할은 대체 가능하다. 카페에 바리스타가 두 명이면, 캐시어는 손님의 커피는 둘 중 어떤 바리스타에게 요청하더라도 문제가 되지 않는다.
책임을 수행하는 방법은 자율적으로 선택할 수 있다. 손님이 주문을 할 때, 카운터 가서 하던 오더 어플을 사용하던 알아서 서로 다른 방식으로 요청을 처리할 수 있다.
한 사람이 동시에 여러 역할을 수행할 수 있다. 그냥 회사에 인원 부족해서 공고와 다르게 풀스택 개발 하는 사람 생각하자.
우리는 벌써 객체지향의 핵심적인 개념을 만났다. 역할, 책임, 협력이다.
인간의 사회를 둘러보면, 개인 혼자만의 힘으로 해결하기 버거운 일들은 협력을 통해 해결한다. 손님이 커피를 만들 줄 모르거나 시간이 없기 때문에 카페에 가서 주문(요청 - request) 하는 것처럼 말이다. 객체지향도 마찬가지다. 손님은 캐시어에게 요청을 하고, 캐시어는 바리스타에게 주문에 맞는 커피를 만들 것을 요청한다. 이렇게 요청은 연쇄적으로 발생한다. 그리고 당연하게 이 요청에 대한 응답(response)도 연쇄적으로 발생한다.
즉, 목표를 위해 협력에 참여하는 객체들은 역할에 맞는 책임을 수행하기 위해, 다른 객체들과 연쇄적인 요청과 응답을 하며 협력 관계를 유지한다.
드디어 우리가 익숙한 단어들이 등장했다. 위의 예시에서 사람 = 객체, 요청 = 메시지, 에이전트가 요청을 처리하는 방법 = 메서드로 바꾸면 대부분의 객체지향을 문맥으로 옮길 수 있게 된다.
객체는 애플리케이션의 기능을 구현하기 위해 존재한다. 간단한 기능도 객체 혼자 감당하기에는 복잡하고 거대하기 때문에, 다른 객체와의 협력을 통해 구현한다. 객체지향 프로그래밍의 아름다움은 협력이 결정한다면, 협력이 얼마나 조화를 이루는지와 같은 품질을 결정하는 것은 객체이다. 이제 객체가 가져야 하는 덕목을 알아보자.
객체는 협력적이어야 한다.
모든 문제를 혼자 해결할 수 있는 전지전능한 객체는 내부 복잡도에 의해 자멸하게 된다. 다른 객체의 요청을 받아 응답을 잘 하도록 만들자.
객체는 자율적인 특성을 가져야 한다.
손님은 캐시어에게 커피를 주문하지만, 손님이 캐시어에게 "내 요청을 바리스타에게 전달할 때, 울면서 춤춰!" 라고 요청하지는 않는다. 그리고 이런 요청이 오더라도 캐시어는 무시해야한다. 바리스타에게 어떻게 요청할 지는 스스로 판단 및 결정하는 자율적인 존재여야 하기 때문이다.
더 설명을 첨부하자면, 객체는 상태와 행위를 하나의 단위로 묶는 자율적인 존재이다. 객체의 관점에서 자율성이란 자신의 상태를 직접 관리하고 상태를 기반으로 스스로 판단하고 행동하는 행위를 수행한다. 바리스타에게 상태란 커피 제조법이고, 행위란 커피 제조법에 맞게 만드는 것이다. 잘 설계된 객체로 구성된 공동체는 유지보수가 쉽고 재사용이 용이하다.
인간은 요청을 하기 위해 다양한 수단을 선택할 수 있지만, 객체간에 협력을 하기 위해서는 메시지 라는 의사소통만 존재한다. 요청하는 쪽은 송신자, 응답하는 쪽을 수신자라고 한다.
수신된 메시지를 처리하는 방법은 메서드이다. 객체지향에서 메서드는 클래스 안에 포함된 함수 또는 프로시저를 통해 구현된다. 그리고 메시지에 대응되는 특정 메서드가 실행되는 것이다. 이렇게 메시지를 수신한 객체가 실행 시간에 메서드를 선택할 수 있다는 점은 다른 프로그래밍 언어와 객체지향 프로그래밍 언어를 구분 짓는 핵심적인 특징 중 하나이다.
그리고 메시지와 메서드를 분리하는 것은 객체들 간의 자율성을 증가시킨다. 캐시어가 손님이 주문한 아이스 아메리카노를 바리스타에게 요청했을 때, 바리스타가 기계로 만들던 수작업으로 만들던 아이스 아메리카노만 응답받으면 된다는 소리다. 위의 짤처럼 말귀를 못알아먹게 만들지만 말자..
객체지향 = 클래스? 진짜 이 주제는 많이 마주쳤다. 본론은 클래스가 중요한 개념이긴 하지만 객체지향의 핵심을 이루는 중심 개념이라고 하긴엔 무리가 있다.
자바스크립트의 경우 프로토타입 기반 객체지향 프로그래밍 언어이다. 클래스가 존재하지않고 오직 객체만 존재한다.(js의 클래스문법을 의미하는것이 아니다.) 그래서 JS는 상속도 클래스가아닌 객체간의 위임 메커니즘 기반으로 한다.
어떤 클래스가 필요한지가 아니라 어떤 객체들이 어떤 메시지를 주고 받으며 협력하는지 집중해야한다.
상태를 가지며 상태는 변경 가능하다.
상태를 변경시키는 것은 행동이다.
행동의 결과는 상태에 의존적이며 상태를 이용해 서술할 수 있다.
행동의 순서가 결과에 영향을 미친다.
어떤 상태에 있더라도 유일하게 식별 가능하다.
객체는 식별 가능한 개체 또는 사물이다. 자동차처럼 만질 수 있는 구체적인 사물일 수도 있고, 시간처럼 추상적인 개념일 수도 있다.
또한, 객체의 다양한 특성을 효과적으로 설명하기 위해서는 객체를 상태(state), 행동(behavior), 식별자(identity)를 지닌 실체로 보는 것이 좋다.
소프트웨어 안에서 객체는 저장된 상태와 실행 가능한 코드를 통해 구현된다.
어떤 행동의 결과는 과거에 어떤 행동들이 일어났었느냐에 의존한다. 자판기에서 원하는 음료수를 뽑았다고 가정하자. 음료수의 가격만큼 돈을 넣어, 원하는 음료수의 버튼이 활성화될 수 있는 상태를 만든 후 버튼을 눌러 뽑게 된다.
이렇게 상태를 이용하면 과거의 모든 행동 이력을 설명하지 않고도 행동의 결과를 쉽게 예측 및 설명할 수 있다. 만약 상태를 이용하지 않는다면, 음료수를 뽑기 전 자판기에 동전이나 지폐를 넣은 행동 및 순서들을 통해 증명해야할 것이다. 상태가 없으면 이 간단한 Flow에서도 벌써 피곤한데, 복잡한 상황에서는 과부하가 올 것이다.
세상에 존재하는 모든 것들이 객체인 것은 아니다. 자판기에 넣은 돈의 '액수'는 객체가 아니다. 자판기마다 매칭되어있는 음료수의 '이름'도 객체가 아니다. 숫자, 문자열, 양, 날짜, 참/거짓등의 단순한 값들은 그 자체로 독립적인 의미를 가지기보다는 다른 객체의 특성을 표현하는데 사용된다.
물론 객체를 이용하여 다른 객체의 상태를 표현할 수도 있다. 조류 애호가의 이름과 나이는 객체가 아니지만, 이 사람의 좋아하는 새들은 name: 참새
와 같이 매칭되어 있는 객체일 수 있다. 이 경우 조류 애호가 객체와 좋아하는 새들 객체는 서로 연결되어 있는 것이다.
이렇게 객체의 상태를 구성하는 모든 특징들이 객체이던 객체가 아니던 통틀어 객체의 프로퍼티라고 한다. age과 같은 프로퍼티는 일반적으로 변경되지 않고 고정되기 때문에 정적이다. 반면 age의 실제 값인 '24'와 같은 프로퍼티 값은 시간의 흐름에 따라 변경되기 때문에 동적이다.
어느 날, 조류 애호가는 조류가 싫어졌다. 이제 조류에 대해 알지 못하는 상태로 변경된 것이다. 이렇게 객체와 객체 사이의 의미 있는 연결을 링크(link) 라고 한다. 객체와 객체 사이에 링크가 존재해야한 요청을 보내고(메시지) 받을 수 있다. 이와 달리 객체를 구성하는 단순한 값(name과 age)은 속성(attribute)라고 한다. 즉, 프로퍼티는 속성과 링크의 조합 으로 표현할 수 있다.
그리고 단순한 값과 객체의 가장 큰 차이점을 식별자의 유무라는 사실도 알아두자.
식별자란 어떤 객체를 다른 객체와 구분하는 데 사용하는 객체의 프로퍼티다. 값은 식별자를 가지지 않기 때문에 상태를 이용한 동등성 검사를 통해 두 인스턴스를 비교해야 한다. 객체는 상태가 변경될 수 있기 때문에 식별자를 이용한 동일성 검사를 통해 두 인스턴스를 비교할 수 있다.
식별자를 지닌 객체를 참조 객체(reference object) 또는 엔티티(entity)라고 한다.
객체의 행동에 의해 객체의 상태가 변경된다. 이 말은 행동이 부수 효과(side effect) 를 초래한다는 것을 의미한다. 자판기에 알맞은 돈을 넣는 행위는 음료수의 버튼을 활성화시키는 부수 효과를 야기하는 것 처럼 말이다. 객체의 행동은 객체의 상태를 변경시키지만 행동의 결과는 객체의 상태에 의존적이다.
행동은 이 두 가지 관점에서 서술할 수 있다.
객체가 다른 객체와 메시지를 통해서만 의사소통 할 수 있다. 이렇게 객체는 협력에 참여하는 과정에서 자기 자신의 상태뿐만 아니라 다른 객체의 상태 변경을 유발할 수도 있다. 자판기에 돈을 넣으면, 나의 지갑 형편도 달라지지만 자판기에 들어가 있는 돈의 액수도 변경된다.
행동은 이 두가지 관점의 부수효과를 서술해야 한다.
객체지향에 갓 입문한 초보자들은 객체에 필요한 상태가 무엇인지를 먼저 결정하고, 그 상태에 필요한 행동을 결정한다. 이 방법은 설계에 나쁜 영향을 미친다.
캡슐화 저해, 객체가 협력자가 아닌 고립된 섬이 되는 문제, 재사용성 저하 등 좋지 않은 상황이 발생할 수 있다. 그러므로 우리는 상태가 아닌 행동에 초점을 맞춰야 한다. 애플리케이션 안에서 어떤 행동을 원하느냐에 따라 어떤 객체가 적합할지 생각하고 설계하자.
객체지향의 사실과 오해라는 책을 아직 2장까지 밖에 읽지 않았는데도 많은 사실들을 알게 되었다. 글을 작성하면서 책의 내용을 많이 가져왔지만 나만의 방식대로 해석한 부분도 있다. 그리고 책에서 매우 좋은 묘사로 객체지향을 설명한 부분도 있는데 가져오지 않은 부분들도 있다. 심오한 내용을 최대한 쉽게, 오해의 소지가 있는 부분들도 정확하게 알려주는 책 같아서 술술 읽기 편한 것 같다. 나머지 부분도 3회정도에 걸쳐 글을 작성할 예정이다.
오해를 하는 상황에서 한번 보고 이해했다고 생각하니, 다음에 다시 봤을때 여전히 오해를 하고 있었으며 또 다시 보고 이해했다고 생각했지만 여전히 오해를 하고 있었고 또 다시 보니 드는 생각은 내가 또 이해한척 하는게 아닐까 하는 의심 이였어요.
책이 말하는 바를 전부 담으신건 아니시겠지만, 필요한 부분을 잘 요약해서 정리해주신것 같아요. 책 저자 입장에선 몹시 맴찢일듯!! 열심히 읽었다고 생각하는? 제 입장에서도 이렇게 잘 정리된 글을 보자니 맴찢이네여. 아아 배아프다...