1. 객체 지향 개념의 원리



 “객체 지향이란 무엇인가 ?” 불행하게도 이 질문에 대한 명확하고 간결한 답은 존재하지 않는다. 대부분의 학문적 개념은 형식적인 정의(formal definition)를 통하여 그 개념이 정립되는 것이 일반적이다. 그러나 “객체 지향”에 대한 학문적 정의는 존재하지 않는다. 왜냐하면 이 질문은 마치 “인간이란 무엇인가 ?”라는 질문과 유사하기 때문이다. 유사 이래로 “인간이란 무엇인가 ?”에 대하여 수많은 철학자들이 그 답을 찾기 위해 노력하였지만 아직도 인간에 대하여 간결하며 엄밀하게 정립된 정의는 없다. 그러나 이 질문에 대한 답이 존재하지 않는다고 해서 우리가 인간을 이해할 수 없는 것은 아니다. 우리는 인간의 여러 속성(예를 들어 “인간은 걸어 다닌다.”, “인간은 생각한다.”, “인간은 웃는다.”, “인간은 사회생활을 한다.”)을 통하여 인간을 이해할 수 있으며 어떠한 것이 인간이고, 어떠한 것이 인간이 아닌가 하는 것을 구별 할 수 있다. 객체 지향에 대한 이해도 이와 마찬가지이다. 우리는 객체 지향에 대한 정의를 통해서 객체 지향을 이해할 수는 없지만 객체 지향이 갖는 특성들을 통해서 이를 이해할 수 있다.


 객체 지향임을 밝히는 필수적인 특성으로 나열될 수 있는 것은 자료 추상화(data abstraction), 상속(inheritance), 다형 개념(polymorphism), 동적 바인딩(dynamic binding) 등이 있다.


2. 기본 지식


2.1 객체 지향 용어


 “객체 지향”이라는 용어는 앞에서 언급한 바와 같이 정확한 정의가 없는 것이 특징이다. 그러나 다음과 같은 방법으로 기술될 수 있다.


 객체 지향 기법에서는 실세계에 존재하는 객체를 소프트웨어로 변환시키는 과정에서 실 객체가 소프트웨어의 객체로 자연스럽게 변환되는 것을 허용한다. 따라서 객체 지향 기법에서는 실세계의 모델링이 용이하며 전체 작업을 객체 위주의 작은 작업 단위로 나누기가 쉽다. 객체 위주로 분리되는 소프트웨어 단위는 이해하고 구현하기가 용이하며 재사용 가능하다.


 소프트웨어의 관점에서 객체 지향 기법은 자료 구조와 그에 대한 연산이 묶여서 구성되는 객체들을 정의하고 이들을 적절히 조직함으로써 소프트웨어 구조를 구성하는 것을 말한다. 따라서 객체 지향 기법은 관습적인 프로그래밍 기법과 비교할 때 자료 구조와 그에 대한 연산의 연결성의 강도에서 차이가 있다. 즉 기존 기법에서는 자료 구조와 연산과의 관계가 느슨한 반면 객체 지향 기법에서는 연결 관계가 강하다.

 

 “객체”와 “클래스”의 구분, 다형 개념의 의미, 추상 클래스의 의미 등이 그 용어를 사용하는 사람마다 다르게 해석되곤 한다. 따라서 객체 지향과 관련된 용어를 이해하고자 할 때 문맥에 따라서 신중을 기해 해석해야만 한다.



2.2 추상화


 소프트웨어를 개발하는 작업의 본질적인 의미는 “simulation”이다. 즉 실세계의 정보나 상황 중에서 주된 관심의 대상이 되는 부분을 컴퓨터 내부로 이식시키는 것이다. 그런데 실세계 상황의 복잡성으로 인하여 실세계의 상황을 바로 컴퓨터 내부로 반영할 수는 없으며 “추상화”와 “구체화” 과정을 거처야 한다. 즉 “추상화” 과정을 통하여 실세계의 상황을 간결하고 명확하게 “모델링”하게 되며 “구체화” 과정을 통하여 추상적 모델을 프로그램으로 변환한다. 시스템의 분석은 “추상화” 과정을 의미하며 이 과정에서 실세계의 상황이 정확하게 모델에 명시가 되지 않으면 프로젝트를 성공적으로 수행할 수 없기 때문에 분석의 중요성이 강조되는 것이다.


 시스템 분석자가 “추상화”를 통하여 이루고자 하는 목적은 두 가지이다. 그 하나는 “어떻게 하면 실제 시스템을 간결하게 표현할 수 있는가 ?”이고 또 다른 하나는 “어떻게 하면 실제 시스템의 정보를 모두 표현 하는가 ?”이다. 그런데 문제는 이 두 개의 질문이 서로 상치된다는 점이다. 즉 어떠한 시스템을 “간결하며 자세히” 표현한다는 것은 마치 동시에 두 마리의 토끼를 잡는 것과 비슷한 상황이기 때문에 결국 한 마리의 토끼도 잡지 못하는 상황에 도달하기가 쉽다. 따라서 분석가는 이러한 두 개의 목표를 잘 조절하여 모델링을 수행하는 능력이 필요하다. 그런데 “추상화” 작업을 이루기 위해서는 두 가지의 전제 조건이 마련되어야 한다.


 그 첫 번째는 “추상화”는 그 모델을 이해할 수 있는 “사회”를 기본 전제로 한다는 것이다. 예를 들면 건축 설계도는 건설업에 종사하는 사람들의 사회에서만 통용되는 추상화 모델이며, 미분 방정식은 수학과 관련된 일을 하는 사람들의 사회에서만 통용되는 추상화 모델이다. 두 번째 조건은 “추상화” 결과를 표현할 수 있는 표기법이 미리 정의되어 있어야 한다는 것이다. 이는 마치 음악가들의 사회에서 악보가 그들의 사고를 전달하는 표기법으로 사용되는 것과 같다. 음악가들은 악보라는 표기법을 이용하여 전 세계의 누구라도 같은 음악을 연주할 수 있는 것이다. 소프트웨어의 요구 사항 분석 결과도 마찬가지로 특정한 표기법을 통해서 표현되며 그 결과는 그 표기법에 익숙한 프로그래머들에 이해될 수 있어야 한다.


2.3 객체와 클래스


 객체 지향 분야에서 “객체”라는 용어와 “클래스”라는 용어가 혼용이 되고 있다는 점은 이들 용어를 사용하는 사람 입장에서 주의해야 할 사항이다. 문맥에 따라 이들을 혼용할 수 있는 이유는 “객체”라는 용어가 “클래스”보다 좀 더 포괄적인 의미를 갖기 때문이다. 더욱이 객체 지향 개념에 대한 깊은 성찰 이후에 깨달을 수 있는 점은 “객체”와 “클래스”의 구별이 큰 의미가 없다는 것이다. 그러나 객체 지향 언어의 관점에서는 “객체”와 “클래스”의 구별을 명확하게 하는 것이 바람직하기 때문에 용어에 대하여 알아야 한다.


 “객체”와 “클래스”의 구별은 기존의 프로그래밍 언어에서 변수에 대한 선언을 예로 들면 이해하기가 쉽다. C 언어에서 정수형 변수 x를 선언하기 위한 문장은 “int x;”이다. 이 문장에서 x는 객체에 해당되며 int는 클래스에 해당된다. 즉 프로그래밍 언어에서의 객체는 어떠한 정보를 표현하기 위해 할당된 기억 장소를 의미하며 클래스는 그 기억 장소의 속성을 기술하는 구조에 대한 명칭이다. 예를 들어 x가 나타내는 실 객체는 다음과 같은 선언을 이용하여 y나 z라는 명칭을 통해서도 접근할 수 있다.


int x; /* 원래 객체에 대한 선언 */

int &y = x; /* 참조형 변수 y를 통한 접근 */

int *z = &x; /* 포인터 형 변수 z를 통한 접근 */


 이러한 선언문 이후에 만약 “x = 10;”라고 쓰거나 “y = 10;”라고 쓰거나 혹은 “*z = 10;”이라고 쓰면 그 결과는 똑 같은 객체 안에 “10”이 저장되는 효과를 얻는다. 따라서 “객체 그 자체”와 “객체를 가리키는 명칭”을 확실히 구분할 수 있어야 한다. 특히 잘 작성된 객체 지향 프로그램에서는 같은 객체를 가리키는 명칭을 만들기 위해 주로 포인터 변수를 이용하므로 포인터 변수에 대한 이해를 분명히 해야 한다. (그림 2.1)는 객체의 명칭이 갖는 의미를 그림으로 나타낸 것이다.

그림 2.1 실 객체와 그에 대한 명칭들을 통한 접근


 정수형 객체 x에 대한 비유를 통하여 실제 객체 지향 프로그래밍 시에 “Student a;”와 같은 방식이 사용된다는 점을 이해할 수 있을 것이다. 이 문장은 “Student 클래스에 해당하는 객체 a를 만들어라.”라는 방식으로 이해하면 된다. 물론 이 때 클래스 “Student”는 이미 존재해야 하며 미루어 생각하건대 이 클래스에는 학생에 대한 정보를 저장할 자료 구조와 그 정보를 다루는 함수가 정의되어 있어야 할 것이다. 지금까지의 설명으로 “객체”와 “클래스”라는 용어의 구분이 이루어졌을 것이다. 다음에는 “객체”와 “클래스”가 어떠한 관계를 맺고 있는가에 대하여 알아보도록 한다.

 “객체”와 “클래스”는 방향성을 갖는 IS_A (is a) 관계를 갖는다. 예를 들어 “김태균은 남자이다.”라는 문장은 영어로 “KimTaegyun is a man.”으로 번역되기 때문에 IS_A 관계라고 한다. 이 문장에서 “김태균”은 객체이고 “남자”는 클래스이다. 그리고 이 문장이 방향성을 갖는다는 의미는 이 문장의 역이 성립하지 않음을 나타낸다. 즉 “남자는 김태균이다.”라는 문장은 성립하지 않는다. 그런데 혼동의 여지가 있는 점은 클래스들 간에도 IS_A 관계가 성립할 수 있다는 것이다. 예를 들어 “남자는 사람이다.” 그리고 “여자는 사람이다.”라는 문장을 사용하였을 경우 “남자”, “여자”, “사람”은 모두 클래스를 나타낸다. 이와 같이 클래스들 간에 IS_A 관계가 성립하는 경우는 “상속”이라는 개념을 통해서 프로그래밍에 반영될 수 있다. 객체 지향 개념에서 “객체”와 “클래스”라는 용어가 혼동이 되는 이유 중의 하나가 이와 같이 “객체와 클래스 간에” 그리고 “클래스와 클래스 간에” 똑같이 IS_A 관계가 성립하기 때문이다. (그림 2.2)는 이들의 차이를 나타내기 위한 그림이다.

그림 2.2 IS_A 관계의 사용


 그림에서와 같이 “객체”와 “클래스”를 그림으로 표현할 때 그 표기법을 달리하는 것이 일반적이다. 이 그림에서와 같이 한 “객체”와 한 “클래스” 간에 IS_A 관계가 성립할 경우 이를 객체 인스턴시에이션(object instantiation)관계라고 말한다. 예를 들어 “김태균” 객체는 “남자” 클래스의 한 인스턴스(instance)이다라고 기술한다. 모델링 시에 “객체”들을 그림으로 나타내면 객체의 수가 너무 많기 때문에 모델이 복잡해진다. 따라서 객체 지향 모델링 시에는 클래스들 만을 이용하여 그림을 그리는데 그 예가 (그림 2.3) 이다. IS_A 관계는 트리 구조로 표현되는데 각종 객체 지향 표기법에서는 이를 위한 독특한 표기법을 사용하고 있다. 앞으로 UML(Unified Modeling Language)이라고 하는 방법론을 주로 설명할 예정이기 때문에 (그림 2.3)는 UML을 이용하여 표현한 것이다. 이 그림에서 나타나는 일반화(generalization)와 상세화(specialization)라는 용어는 클래스들 간의 상속 관계를 방향성을 기준으로 지칭할 때 사용된다. 즉 “사람” 클래스의 특수한(상세화된) 경우가 “남자” 클래스이며, 역으로 “남자” 클래스를 일반화한 결과가 “사람” 클래스이다라고 기술한다. 클래스 간의 IS_A 관계는 다른 말로 “OR 관계”로 지칭되기도 한다. “OR 관계”로 지칭되는 이유는 (그림 2.3)의 경우 “사람의 종류에는 남자 OR 여자가 있다.”라고 기술될 수 있기 때문이다.

그림 2.3 IS_A 관계를 나타내는 UML 표기법



2.4 “같은 것” 과 “다른 것”


 객체 지향 분석을 할 경우에 분석가가 갖추어야 할 통찰력 중의 하나가 “같은 것”과 “다른 것”을 구별할 줄 아는 능력이다. 객체들 간의 동질성과 이질성을 구별해야만 우리는 동일한 객체들을 묶어서 하나의 클래스로 정의할 수 있다. 또한 객체들 간의 구분만 아니라 클래스들 간의 구분을 통해서 정확한 상속 계층을 유도할 수 있다. 즉 “객체”들의 구분을 통해서는 새로운 클래스를 유도할 수 있고 “클래스”들의 구분을 통해서는 클래스들 간의 상속 구조를 유도할 수 있다.


 어떠한 두 개의 사물이 “같은 가” 혹은 “다른 가”를 구별 하는 작업이 생각처럼 쉽지는 않다. 예를 들어 “비행기와 달이 같은가 혹은 다른가 ?”라는 질문에 대하여 생각해 보자. 이 질문은 다소 황당하며 대부분은 당연히 비행기와 달이 다르다는 결론을 내릴 것이다. 그러나 실제 답은 그렇지 않다. 우리가 어떠한 사물을 구분 짓기 위해서는 그 사물을 보는 관점의 기준을 우선적으로 설정하여야 한다. 즉 “비행기와 달은 하늘에 떠 있다는 점에서 같다.”라고 대답할 수 있다. 만약 우리가 어떠한 소프트웨어를 개발하는데 그 소프트웨어의 목적상 객체들을 “하늘에 떠 있는 것”과 “땅에 붙어 있는 것”으로 구별해야만 한다면 “달”과 “비행기”는 같은 클래스에 포함되어야 한다. 그리고 “사람”과 “바퀴 벌레”는 땅에 붙어 있다는 점에서 같은 클래스로 묶어야 할 것이다. 또한 객체나 클래스를 구분 짓는 관점의 기준은 상대적인 것이기 때문에 어떠한 동질성이나 이질성은 영원할 수가 없으며 응용 분야에 따라 달리 모델링 되어야 한다. 예를 들어 객체의 모습을 기준으로 객체를 구별해야 할 경우에는 “달과 동전이 같다.” 라고 말할 수 있으며 “달과 비행기는 다르다.”라고 말할 수 있다.


 사물의 공통점을 이용하여 실세계를 이해하는 기법은 어린 아이들에게서 쉽게 찾아 볼 수 있는 현상이며 따라서 우리는 어린 아이들이 언어를 배우고 사물을 익혀 가는 과정을 주의 깊게 살펴볼 필요가 있다. 실제 Xerox의 연구팀이 Smalltalk이라는 언어를 설계하면서 추구하였던 방향이 이와 유사하다.


2.5 공급자와 소비자


 객체 지향 개념과 관련하여 독자들이 익숙해져야 하는 용어는 “공급자”(supplier)와 “소비자”(consumer)이다. 공급자는 어떠한 클래스를 제작하여 다른 사람이 이를 사용할 수 있도록 제공하는 사람을 의미하며 소비자는 제공된 클래스의 서비스를 이용하는 사람을 의미한다. “어째서 공급자와 소비자 관점의 분리가 필요 한가?”를 생각해 보자. 예를 들어 100 명의 요원이 참가하는 프로젝트에 참여하고 있다고 가정하자. 그러면 그 프로젝트를 수행하는 각자의 임무는 다른 사람의 임무와 어떠한 관계를 가지는가? 아마도 임무는 다른 사람의 임무와 일 부분은 관계를 맺으며 나머지 부분은 다른 사람의 임무와 관계를 맺지 않을 것이다. 즉 프로젝트에 개입하고 있는 독자가 처해지게 될 일반적인 상황은 다른 사람이 제공하는 서비스를 이용하여 자신에게 할당된 임무를 수행하는 것이고, 또한 독자가 전체 프로젝트의 일 부분으로 작성한 소프트웨어 부품을 다른 사람이 이용할 수 있게 마련하는 것이다. 따라서 프로젝트에 참여하고 있는 모든 사람은 한편으로는 “소비자”의 입장에, 다른 한편으로는 “공급자”의 입장에 있게 된다. 예를 들어 다른 사람이 만들어 놓은 “Linked List” 클래스를 이용하여 다른 사람이 사용할 수 있는 “Stack”이라는 클래스를 제공하는 입장에 있을 수 있다.


 객체 지향 개념에서 “공급자”와 “소비자” 관점을 분리하는 이유는 명세(specification)와 구현(implementation)의 분리를 통한 정보 은닉(information hiding)을 달성하기 위함이다. 만약 100 명이 참여하고 있는 프로젝트에서 어느 한 사람이 자기의 임무를 수행하기 위하여 다른 사람들(즉 나머지 99 명)이 한 일을 모두 이해해야만 한다면 어떠한 문제가 야기될 것인가 ? 아마도 다른 사람이 작성한 코드를 읽느라 거의 모든 시간을 소비해야 할 것이다. 이러한 경우 100 명이 모두 같은 상황에 처해졌다고 할 수 있기 때문에 결국 프로젝트를 진행시키지 못할 것이 분명하다. 따라서 같은 소프트웨어 부품에 대하여 공급자의 입장과 소비자의 입장을 나누어 접근할 필요가 있으며 특히 소비자 입장인 경우 최소의 노력만으로 그 소프트웨어 부품에 대하여 이해할 수 있도록 하는 방법이 필요한데 이것이 “명세”이다. 소프트웨어 부품의 “명세”는 그 소프트웨어가 하는 일을 가장 쉽게 이해할 수 있도록 명시한 부분을 의미한다. C++ 프로그램의 경우 어떠한 클래스의 “명세부”는 public 문장에 의하여 외부에 공개되는 멤버 함수 이름들을 말한다. 그리고 “구현부”는 그 소프트웨어 부품을 만드는 사람이 알아야 할 모든 내용을 의미한다. 한 클래스의 내부 자료 구조와 함수들의 본체가 “구현부”이며 이들은 클래스 공급자의 책임 하에 작성되고 그 클래스의 소비자에게는 숨겨져 있어도 되는 부분이다. 이러한 관계는 마치 감기에 걸린 사람이 “아스피린”을 사 먹는 상황과 비슷하다. “아스피린”이라는 약을 보는 소비자의 입장은 단순히 약을 어떻게 먹을 것인가하는 방법과 그 약이 감기를 낫게 한다는 효능에 대해서만 알면 되지 그 감기 약의 성분이 화학적으로 어떻게 구성되어 있으며 그 약의 질량이 얼마 인가하는 문제는 알 필요가 없는 것이다. 즉 “감기를 낫게 하는 효능” 부분이 아스피린의 “명세부”이며 “화학적 성분 및 제조 과정, 질량 등”은 아스피린의 “구현부”이다. 따라서 아스피린의 “구현부”에 해당하는 책임은 전적으로 공급자인 제약 회사의 몫이며 소비자에게는 알려질 필요가 없다.


 소프트웨어 부품을 다룰 때 “명세부”와 “구현부”를 철저히 구분하고자 하는 노력은 객체 지향 개념이 언급되기 이전부터 이루어졌다. 그러나 과거에는 주로 하나의 함수를 대상으로 이를 실현하였으며 객체 지향 기법에서는 클래스 단위로 이를 실현하기 때문에 시스템의 복잡도를 극복하는데 큰 도움이 된다. 어쨌든 “명세부”와 “구현부”의 분리를 통하여 소프트웨어 부품의 상세한 내용을 외부 소비자가 모르도록 하는 원리를 “정보 은닉”이라 한다. “정보 은닉”을 표현하는 또 다른 용어는 “블랙 박스 접근 방식”(black box approach)이다. 


2.6 객체의 부속품


 실 세계의 객체들이 갖는 공통적인 특징은 대부분 경우 어느 한 객체가 다른 부속 객체들의 조합에 의해 구성된다는 것이다. 예를 들어 “사람” 객체는 “머리”, “몸통”, “팔”, “다리”라는 객체들이 모여서 만들어지며 “자동차” 객체는 “엔진”, “샤시”, “바퀴” 등의 객체가 모여서 이루어짐을 알 수 있다. 따라서 우리는 어떠한 객체를 해석할 때 그 객체의 구성 요소들을 통하여 이해할 수 있다. 객체의 부품들을 나누어 이해하고 개별 부품을 설계한 후에 이들을 모아 주 객체를 설계하는 방식은 전체 임무를 나누어 수행하는 “분할 정복”(divide and conquer)의 의미를 갖는다. “분할 정복”도 “정보 은닉”의 원리와 마찬 가지로 소프트웨어 공학자들이 오랜 기간 추구해 온 소프트웨어 개발 방식이다.


 주 객체와 부속 객체와의 관계는 HAS_A (has a) 관계로 표시된다. 예를 들어 “자동차는 엔진을 구성 요소로 갖는다.”라는 문장은 “A car has an engine.”으로 번역되기 때문에 HAS_A 관계라 한다. 그런데 역으로 “엔진은 자동차의 부품이다.” 즉 “An engine is a part of a car.”라는 문장도 사용될 수 있기 때문에 이 관계는 PART_OF (part of) 관계라고 불리기도 한다. 특히 이러한 관계를 명시하는 용어가 객체 지향 사회에서 “집합화”(aggregation)라는 이름으로 사용된다. “집합화”는 “일반화” 경우와 마찬가지로 계층적 트리(hierarchical tree)로 표현되는데 (그림 2.4)는 “사람”의 구조를 계층적으로 표현한 예이다. 이 그림에서 사용된 표기법도 IS_A 경우와 마찬가지로 UML 표기법에 의한 것이다. 이미 언급했다시피 IS_A 관계와 HAS_A 관계는 전혀 별개의 관계이기 때문에 모든 방법론에서 서로 다른 표기법을 사용한다. HAS_A 관계는 다른 말로 “AND 관계”로 지칭되기도 한다. 그 이유는 (그림 2.4)의 경우 “사람은 머리 AND 몸통 AND 팔 AND 다리로 구성된다.”라는 방식으로 기술되기 때문이다.

그림 2.4 UML 표기법으로 표현된 HAS_A 관계


 HAS_A 관계로 분석되는 결과는 실제 프로그래밍 시에 프로그램에 그대로 반영되기 때문에 객체의 본질적 특성에 맞추어 이 관계를 추출하는 작업은 매우 중요하다. 실제로 시스템을 분석하여 얻게 되는 모델에 포함되는 중요 정보의 대부분은 IS_A 관계와 HAS_A 관계들이다.



+ Recent posts