본문 바로가기

책추천 「객체지향 사실과 오해 (1)」상태 | 행동 | 캡슐화 |식별자

by Recstasy 2019. 8. 27.

 객체지향 설계는 현실 세계를 모방하는 것이 아니다.

 

현실세계를 기반으로 새로운 세계를 창조하는 설계가 객체지향 방식이다. 그래서 프로그램 속의 세계에서는 돈이 자율적으로 움직이고, 계산기가 알아서 결정을 내린다. 객체지향 세계에서는 현실세계에서 자율적으로 행동하지 못하는 객체들이 자율적으로 움직인다. 객체지향을 지향하는 개발자는 마치 '이상한 나라의 엘리스'를 창조하는 기분으로 가상의 세계를 창조하는 창조자인 셈이다. 

 

 

 

상태- 행동

 객체지향 설계에서 객체는 '상태'와 '행동'을 갖고 있다. 객체의 상태를 결정하는 것은 행동이지만 행동의 결과를 결정하는 것은 상태다. 가령, 동화 '피노키오'를 생각해보자. 알다시피 피노키오의 코는 거짓말을 하면서 길어진다. 이를 객체지향 관점에서 생각해보면, 피노키오라는 객체(클래스:사람)는 거짓말을 하는 '행동'에 의해 상태(코의 길이)가 결정된다. 역으로 코의 상태를 결정하는 것 역시 피노키오의 행동이다. 상태가 행동을 결정하고, 또 행동이 상태를 결정한다. 상태-행동은 상호보완적이며, 이를 통해 객체는 자율적인 존재가 된다. 또한, 현재 '행동'의 결과는 과거 행동에 의해 영향을 받는다.

 

 가령, 피노키오가 맛있는 사탕을 더 요구하기 위해서는 할아버지의 물음에 답을 해야 하는데, 거짓말을 할수록 코가 길어지기 때문에 과거에 했던 행위에 영향을 받을 수밖에 없다. 즉, 객체의 메서드(행위)는 이전에 수행된 메서드에 영향을 받는 셈이다. 이는 행동(메서드)간의 순서가 중요하다는 것을 의미한다. 

 

 

 

식별자

 피노키오의 사례에서 '피노키오'는 '사람'클래스의 인스턴스 객체이다. 피노키오라는 객체는 상태가 어떻게 변하건(코의 길이, 키, 몸무게 등..)여전히 피노키오다. 피노키오의 코가 엄청 길어졌다고 해서 피노키오라는 객체 자체가 변하지는 않는다. 이렇게 상태 변경과 무관하게 유일한 존재로 식별가능한 객체를 '식별자'라 한다. '상태 - 행동 - 식별자'는 객체의 특징이다. 이를 토대로 객체지향 세계에서 객체의 특징을 정리하면 다음과 같다. 

 


1) 피노키오는 상태를 가지며 상태는 변경 가능하다.

 

2) 피노키오의 상태를 변경시키는 것은 피노키오의 행동이다.

  (행동의 결과는 상태에 의존적이며 상태를 이용해 서술할 수 있다)

  (행동의 순서가 결과에 영향을 미친다)

 

3) 피노키오는 어떤 상태에 있더라도 유일하게 식별 가능하다.


 

 객체지향 세계에서의 객체는 식별가능하며, 추상적인 개념을 내포한다. 즉, 객체는 '구별 가능한 식별자', '특징적인 행동', '변경 가능한 상태'를 갖는다. 이로써 소프트웨어 내의 객체는 저장된 상태와 실행 가능한 코드를 통해 구현된다. 다시 강조하자면, '상태 - 행동 - 식별자'는 객체지향 설계에서 객체가 갖춰야 할 필수덕목이다. 상태를 통해 행동을 결정하고, 또 행동을 통해 상태를 결정한다. 그리고 객체마다 자신만의 행동과 상태를 갖고 있기에 식별가능하다. 

 

예를들어, 지하철을 탑승하는 상황을 생각해보자. 지하철을 타기 위해 체크카드(교통카드)를 꺼낸다. 여기서 카드는 객체다. 또, 카드를 대는 개찰구 역시 객체이며, 지하철, 역무원, 지나가는 사람들도 모두 객체다. 단, 카드에 있는 잔액은 객체가 아닌 값이다. 값은 '잔액'이라는 속성을 통해 현재 상태를 알려준다. 이렇게 객체는 속성을 가지며, 속성의 값을 통해 상태를 알 수 있다. 또, 상태에 따라 상대 객체의 행동(개찰구 열림, 닫힘)과 상태가 결정된다. 이때 객체들은 협력을 통해 다른 객체들의 상태를 알 수 있고, 또한 어떠한 객체가 연결되어 있는지를 전달한다. (지하철 개찰구 객체는 카드 단말기 객체와 연결되어 있음) 이를 표현한 것이 아래 UML 다이어그램이다.  

 

개찰구 
pass:false
...  


open(){ }
...  

 

   
   

 

 카드 단말기
 cash : 120,000
...


check(){} 
...

 

 위의 다이어그램에서 객체와 객체 사이의 연결된 선들을 '링크'라 부르며, 링크를 통해 객체들간의 메시지가 오고간다. 그리고 이 메시지들은 '인터페이스'가 된다. 여기서 주의할 점은 객체가 자유로워야 한다는 것이다. 객체는 타객체로부터 메시지를 요청하고, 받을 수는 있지만 다른 객체의 행위(메서드)나 상태에 직접 개입해서는 안 된다. 객체의 메서드나 상태를 변경시킬 수 있는 주체는 오직 메시지를 요청받은 객체뿐이며, 이는 해당 객체의 고유한 권한이다. 

 스파게티처럼 꼬여있는 코드를 보면 대개 객체의 협력이 아닌 객체의 협박이 눈에 보이는 경우가 많다. 객체의 상태는 어떠한 경우건 저절로 변경되어서는 안 된다. 객체의 상태를 변경하는 것은 객체의 자발적인 행동뿐이다. 가령, 피노키오의 코가 줄어든 이유는 피노키오가 거짓말을 했던 행동에 있으며, 피노키오의 코는 거짓말과 관련된 특정 행동을 할 때마다 스스로 길이가 변한다. 만일 피노키오의 친구들이 멋대로 피노키오의 코를 마음대로 조종할 수 있다면, 피노키오가 있는 동화세계는 무너진다. 객체의 행동과 상태는 다음과 같은 관계가 되어야 한다.

 

 - 객체의 행동은 상태에 영향을 받는다

 - 객체의 행동은 상태를 변경시킨다

 

 

객체지향 구현에서 '클래스', '객체 인스턴스', '상속'과 같은 주제와 달리 '식별자'는 크게 중요하게 다루지 않는다. 구현은 어차피 프로그래머의 몫이라 생각하기 때문이다. 하지만 식별자는 '설계'에 있어 중요하다. 너무 당연해서 식별자를 인식하지 못하는 경우가 많을 뿐이다. 가령, 다음 질문을 논리적으로 생각해보자. 

 

"값과 객체의 차이는?"

 

 'Int a' 변수에 담겨있는 값과 Person 클래스를 상속받은 officer 객체와의 차이점은 뭘까? int a값은 value클래스를 상속받았고, officer객체 역시 Person클래스를 상속받았다. 숫자값도 객체다. 즉, 값도 객체이며, officer역시 객체다. 무슨 차이일까? 현실세계를 생각해보자. 만일, 우리가 등록한 수영장에 '나'와 똑같은 '이름', '키', '몸무게'를 갖고 있는 사람이 있다고 한다면, 그 사람과 '나'는 똑같은 사람일까? 아마도 정신질환자가 아닌 이상 '다르다'고 답할 것이다. 이름과 키, 몸무게는 현재의 상태이다. 그리고 상태가 같다고해서 똑같은 사람으로 볼 수 없다. 객체지향의 세계에서는 상태로써 객체의 동질성을 판단할 수 없다. 상태는 변하기 때문이다. 

 

반면, 초등학교 시절의 당신과 현재의 당신은 같은 사람(객체)인가? 당연히 답은 '같다'가 될 것이다. 초등학교 시절에도 당신은 xxx라는 사람의 x번째 자식이었고, 지금도 그렇고 심지어 죽고 나서도 영원히 그렇게 기록에 남을 것이다. 정리하자면, 우리는 이렇게 불변하는 값(정보)이 두고 각 개체간의 동질성을 판단할 수 있다. 생물학적으로는 유전자와 게놈지도와 같은 정보가 될 수 있고, 사회적으로는 주민등록번호, 개인적으로는 부모와 자식간의 관계 정보가 '식별 정보'가 될 수 있다. 

 

 우리는 변하지 않는 식별정보를 통해 각 객체를 식별할 수 있으며, 해당 정보로써 객체간의 관계를 판단할 수 있다. 즉, 식별자가 있기 때문에 객체가 어떤 상태에 있더라도 유일하게 식별 가능하다.

 

 

 

협력과 행동

객체가 다른 객체와 협력하는 유일한 방법은 다른 객체에게 요청을 보내는 방법 뿐이다. 요청을 수신한 객체는 요청을 처리하기 위해 적절한 방법에 따라 행동한다. 다시 강조하지만, 객체의 행동은 객체가 협력에 참여할 수 있는 유일한 방법임을 명심하자. 객체는 오직 메시지를 통해서만 다른 객체와 의사소통할 수 있다. 만일 피노키오가 코를 줄이는 음료를 마셨다고 한다면, 음료수라는 객체의 상태가 변하고, 피노키오의 상태 역시 변하거나 혹은 변하지 않는다.  여기서 피노키오의 행위는 자신의 상태와 동시에 음료수라는 객체의 상태도 변하게 한다. 이를 객체지향 세계의 특징에 접목해보면 다음과 같은 특징을 뽑아낼 수 있다.  

 

- 객체 자신의 상태변경

- 행동 내에서 협력하는 다른 객체에 대한 메시지 전송 

 

"행동이란 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동이다. 객체는 행동의 결과를 통해 자신의 상태를 변경하거나 다른 객체에게 메시지를 전달할 수 있다. 객체는 행동을 통해 다른 객체와의 협력에 참여하므로 행동은 외부에 가시적이어야 한다(인터페이스).

 

 

 

캡슐화

 캡슐화는 객체지향 설계의 단골메뉴다. 많은 이들이 객체지향의 기본개념으로 '캡슐화'를 꼽는다. 캡슐화는 왜 중요할까? 캡슐화는 말 그대로 캡슐에 싸듯이 객체 내부의 상태를 외부에 숨기며, 외부에서 내부를 간섭하지 못하게 하는 의미다. 그런데 왜 객체지향의 원리에 캡슐이 필요할까? 

다시 지하철 개찰구에 교통카드를 찍는 상황을 떠올려보자. 교통카드 단말기는 '잔돈확인'이라는 메시지를 카드사 데이터베이스 객체에 전달한다. 여기서 교통카드 단말기의 역할은 잔돈을 확인하는 것이며, 잔돈 확인을 통해 '통과 or 금지'를 알려줄 책임을 갖는다. 교통카드 단말기는 직접 고객의 은행DB에 접근할 수 없으며, 은행DB는 잔돈확인 메시지를 요청받고, 자신의 상태를 변경(잔돈이 있으면 요금만큼 뺄셈)하는 기능(메서드)을 담당한다. 만일 교통카드 단말기가 직접 고객의 은행DB에 접근해서 명령을 내릴 수 있다면 어떻게 될까? 아마도 해커들이 아주 좋아할 만한 상황이 될 것이다. 굳이 메인 DB에 접근하지 않더라도, 교통카드 단말기만 조정해서(악성 프로그램) 고객의 DB정보를 빼낼 수 있기 때문이다. 따라서 객체는 메시지를 보내고 받을 뿐, 직접 다른 객체의 행동과 상태를 변경해서는 안 된다.

 

 객체끼리는 서로 전달받고 보내는 메시지만 확인할 수 있을 뿐, 다른 객체 내부의 사정은 알지 못한다. 객체가 외부에 노출하는 것은 오직 공개된 행동(메시지)밖에 없다. 메시지를 해석하고 그에 반응해서 상태를 변경할 지 여부는 전적으로 메시지 수신자의 자율적인 판단에 달려 있다. 그래서 은행DB는 카드 단말기가 보내온 메시지(ex.잔돈 확인)를 거부할 수도 있다.(은행DB점검 기간) 송신자가 수신자의 상태에 간섭할 수 있는 어떤 권한이 없어야 자율적인 객체지향 세계가 만들어진다. 이것이 캡슐화가 필요한 이유다. 상태를 잘 정의된 행동의 뒷면으로 캡슐화하는 것은 객체의 자율성을 높이고 협력을 단순하고 유연하게 만든다. 객체지향 설계에서 캡슐화가 그렇게 강조되는 이유가 여기에 있다.

 

 

 

 

객체지향설계의 핵심

 객체지향 세계를 창조하는 개발자들의 주된 업무는 객체의 상태를 조회(Query)하고 객체의 상태를 변경하는 것(Command)이다. 객체가 외부에 제공하는 행동의 대부분은 쿼리와 명령으로 구성된다. 이와 관련하여, 버트란드 마이어(Object-Oriented Software Construction)[Meyer.2000]는 객체를 기계에 비유했다. 

마이어가 제시한 기계는 사각형 버튼과 둥근 버튼 그리고 디스플레이 표시창이 있다. (마치 휴대용 라디오와 같은 형상) 사람은 기계 내부의 작동원리는 알 수 없고, 오직 둥근 버큰과 네모 버튼을 통해 기계의 동작을 제어할 수 있다. 사각형 버튼(음료를 마신다, 부채질하다, 버섯 먹다, 문을 통과하다)은 객체의 상태를 변경할 수 있고, 둥근 버튼을 통해 객체의 상태를 디스플레이에 조회할 수 있다. 또, 좌측 상단의 슬롯버튼을 눌러 기계의 상태를 변경하거나 조회할 때 기계가 필요로 하는 추가 정보를 위치할 수 있다.  즉, 사각 버튼은 Command(명령)를 의미하고, 둥근 버튼은 Query(조회)를 의미한다. 여기서 중요한 것은 명령(Command) 버튼과 조회(Query)버튼 이외의 다른 방법을 통해서 기계를 사용할 수 없다는 점이다. 사용자는 기계를 열어서 내부를 볼 수 없으며, 오직 버튼을 통해서만 접근가능하다.(캡슐화) 

 

기계들은 서로 연결될 수도 있는데, A라는 기계에서 '음료를 마신다'라는 사각형 버튼을 누르면 A와 연결된 B라는 기계에 '마셔지다(?)'라는 메시지가 전달된다. B는 메시지를 전달받고, 자율적으로 음료를 마시는 동작을 수행한다. 객체지향 관점에서 이것은 '음료를 마시다'라는 메시지를 수신한 객체가 메시지를 처리하던 도중, 음료 객체에게 '마셔라'라는 메시지를 전송한 것과 같다. 즉, 링크를 통해 연결된 두 객체가 메시지 전송을 통해 협력하고 있는 것이다. 

 

메이어가 제시한 객체를 기계로서 바라보는 관점은 '상태', '행동', '식별자'에 대한 시각적인 이미지를 제공하고 캡슐화와 메시지를 통한 협력 관계를 매우 효과적으로 설명한다. 결국, 메이어가 말하고자 하는 바는 명확하다. 

 

"행동이 상태를 결정한다"

 

 

 

 

객체지향의 함정

 객체지향에 갓 입문한 사람들이 가장 쉽게 빠지는 함정은 상태를 중심으로 객체를 바라보는 것이다. 초보 개발자들은 객체지향 설계를 할 때, 먼저 객체에 필요한 상태가 무엇인지를 결정하고 그 상태에 필요한 행동을 결정한다. 가령, 피노키오라는 객체를 설계할 때 초보자들은 피노키오 객체에게 필요한 상태가 무엇인지를 찾고나서 코의 길이와 위치와 같은 속성을 피노키오에 추가한다. 그리고 코의 길이와 위치를 변경하거나 조회할 수 있는 행동이 무엇인지를 고민하다. 

하지만 상태를 먼저 결정하고 행동을 나중에 결정하는 방법은 설계에 나쁜 영향을 끼친다. 가령, 피노키오 코의 길이(상태)를 결정해버리면 해당 속성을 숨겨야 할지 드러내야 할 지 모르게 된다. 이렇게 구현하다보면 캡슐화가 뒤죽박죽 된다. 또한 상태를 먼저 결정하다보니 협력 객체와의 관계가 무시되고, 자연스럽게 협력에 적합하지 못한 '신'적인 객체가 탄생한다. 만일 신적인 객체가 한번 꼬이면 시스템 전체가 붕괴될 수도 있다. 그 결과 코드의 재사용이 어렵게 된다. 협력이 되지 않기 때문에 당연히 재사용이 어려울 수 밖에 없는 것이다. 

 

그렇다. 객체지향 설계가 무너지는 이유는 '협력'에 있고, 결국 '협력'이 문제다. 객체지향 설계를 아름답게 하려면 상태가 아닌 '행동'에 초점을 맞춰야 한다. 객체는 다른 객체와 협력하기 위해 존재하기 때문이다. 객체의 행동은 객체가 협력에 참여하는 유일한 방법이다. 객체가 적합한지를 결정하는 것은 그 객체의 상태가 아니라 '행동'이며, 새로운 세계를 창조하는 설계자로서의 개발자는 협력의 문맥에 맞는 적절한 행동을 수행하는 객체를 발견하거나 창조해야 한다. 

 

 결과적으로 우리가 어플리케이션 안에서 어떤 행동을 원하느냐가 어떤 객체가 적합한지를 결정한다. 객체의 적합성을 결정하는 것은 상태가 아니라 언제나 객체의 행동이다. 행동을 결정한 후에야 행동에 필요한 정보가 무엇인지를 고려하게 되며 이 과정에서 필요한 상태가 결정된다. 따라서 먼저 객체의 행동을 결정하고 그 후에 행동에 적절한 상태를 선택해야 한다. 그리고 협력 내에서 객체의 행동은 결국 객체가 협력에 참여하면서 완수해야 하는 책임을 의미한다. 이를 '책임-주도 설계'라 하는데, 이 모든 것의 시작은 '행동이 상태를 결정한다'는 것에 있다.

 

 

 

 

은유

 아직도 많은 객체지향 개발관련 강의에서, 객체지향 설계는 현실세계를 '관찰'하고 최대한 비슷하게 '모방'하는 것이라 강조한다. 이는 틀렸다. 객체지향 설계가 현실을 모방한다는 개념은 완전 틀렸다. 루이스 캐럴의 '이상한 나라의 엘리스'를 생각해보자. 동화의 세계에서는 무생물인 트럼프 카드가 스스로 움직이고, 전화가 알아서 전화를 걸고 받는다. 객체지향을 설계하는 개발자도 루이스 캐럴의 '이상한 나라의 엘리스'와 같은 세계를 창조하려고 노력해야 한다. 

왜일까? 

 

 가령, 카페에서 주문하는 어플리케이션을 객체지향 프로그램으로 설계한다고 생각해보자. 현실세계를 그대로 관찰하고 표현한다면, '손님'이란 객체가 메뉴판에서 커피를 선택하고, 바리스타한테 말을 하고, 벨을 받는다. 현실에서 커피 메뉴판의 커피명은 손님의 머리속에 있다. 손님은 자신이 기억한 커피를 주문한다. 이를 코드로 표현하면, 손님이란 객체는 메뉴판 객체의 커피메뉴를 바리스타에게 말하기 위해서 기억해야 한다. 메시지만 보내는 것으로 끝나지 않고 바리스타에게 주문을 해야하기 때문이다. 

 

하지만 어플리케이션은 어떤가?

 

 손님이 전자메뉴판에서 주문하고 난 뒤 다시 바리스타에게 가서 주문내용을 말해야할까? 아마도 그런 멍청한 프로그램은 없을 것이다. 손님은 메뉴에서 주문 메시지만 보낼뿐이다. 다른 행동은 힐 필요가 없다. 메뉴판은 마치 사람처럼 손님의 선택을 받아서 바리스타에게 전달한다. 마치 메뉴판이 사람처럼 행동한다. 여기서 '사람처럼' 이 부분이 아주 중요하다. 객체지향 세계는 루이스 캐럴의 '이상한 나라의 엘리스'이기 때문이다. 객체지향 세계에서 현실에 비유되는 도메인(무생물)은 모두 인간처럼 행동하고, 상태를 갖고 속성값 및 식별자가 있다. 객체지향 세계속의 객체들을 억지로 현실세계의 개념에 끼워 맞춘다면, 이들의 관계가 굳어지고 결국 유연하지 못한 설계가 완성된다. 만일 커피메뉴를 다시 손님이 기억해야 한다면, 해당 메뉴판 프로그램은 '다른 요식업'에 그대로 사용할 수도 없다. 확장성이 없기 때문이다. 

 

 객체지향 설계자로서 개발자의 목적은 현실을 모방하는 것이 아니다. 단지 이상한 나라를 창조하기만 하면 된다. 현실을 닮아야하는 어떤 제약이나 구속 및 간섭도 없다. 개발자 스스로 창조한 객체의 특성을 상기시킬 수 있다면 현실 속 객체의 이름을 이용해 객체를 묘사할 뿐이다. 그렇지 않다면 깔끔하게 현실을 무시하고 자유롭게 개발자가 생각하는 새로운 세계를 창조해야 한다. 레베카 워프스브록은 이를 '객체 특징의 의인화'라 불렀다. 객체지향 세계에서 객체들은 스스로 말을 하고, 이동하며 선택하고 판단한다. 현실에서 가만히 있던 객체들은 객체지향 세계 속에서 모두 사람처럼 살아난다. 

 


"객체는 무생물이거나 심지어는 실세계의 개념적인 개체로 모델링될 수도 있지만, 그것들은 마치 우리가 현실 세계에서 에이전트로 행동하는 것처럼 시스템 안에서 에이전트처럼 행동한다. 객체가 현실 세계의 대상보다 더 많이 안다는 것이 모순처럼 보일 수도 있다. 결국, 인간이라는 에이전트 없이 현실의 전화는 서로에게 전화를 걸지 않으며 색은 스스로 칠하지 않는다. 일상적인 체계에서는 어떤 사건이 일어나기 위해 반드시 인간 에이전트가 필요한 반면 객체들은 그들 자신의 체계 안에서 "능동적이고 자율적인" 에이전트다. 의인화의 관점에서 소프트웨어를 생물로 생각하라. 모든 생물처럼 소프트웨어는 태어나고, 삶을 영위하고, 그리고 죽는다.

-1990. Wirfs-Brock-


 

댓글

최신글 전체

이미지
제목
글쓴이
등록일