메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

추상 자료형

한빛미디어

|

2004-08-24

|

by HANBIT

22,665

저자: 주우석 / 명지대 컴퓨터공학과 교수

출처: IT CookBook, C C++로 배우는 자료구조론(한빛미디어, 2004)


자료구조론에서 가장 중요한 개념 중 하나가 바로 추상 자료형(抽象 資料型, ADT: Abstract Data Type) 또는 추상 데이터 타입 개념이다. 추상 자료형은 프로그램의 대상이 되는 사물이나 현상을 추상화하여 정의한 것이다. 추상화 되어 있기 때문에 그 자료형은 일반적으로 정의되어 있고 따라서 구체적인 여러 가지 경우에 재사용될 수 있다. 추상화 되어 있기 때문에 그 자료형의 세부적인 내용을 모르는 여러 사용자도 이용할 수 있다.

따라서 추상 자료형을 사용하는가 아닌가는 프로그램 작성의 효율에 지대한 영향을 미친다. 1970년대에 도입된 이 추상 자료형 개념은 여러가지 중요한 특성을 지닌다. ‘자료구조’는 단지 추상 자료형을 구성하는 하나의 요소에 불과하다. 여기에서는 추상 자료형 개념이 도입된 배경과 그 특징을 알아보고, 이러한 추상 자료형이 C와 C++에 어떻게 적용되는지를 설명하기로 한다.


Ⅰ. 추상 자료형

1. 사용과 구현의 분리

추상 자료형의 가장 큰 특징 중 하나가 사용자와 구현자의 분리다. 즉, 내가 어떤 데이터 타입을 추상적으로 정의해서 사용하였다면, 실제로 그 데이터 타입을 구체적으로 구현하는 것은 남에게 맡길 수도 있다는 시각이다. 다시 말해 나로서는 남들이 구현한 다양한 추상 자료형을 힘들이지 않고 가져다 쓰기만 하면 된다. 역으로, 내가 구현한 것이 있으면 남들도 이를 쉽게 가져다 쓸 수 있다. 추상 자료형은 이처럼 사용하는 관점과 구현하는 관점을 명확히 분리함으로써, 하나의 프로그램을 여러 사람이 나누어서 짜는 데 매우 유용하다.

2. 사용자 관점

추상 자료형(ADT: Abstract Data Type)은 문자 그대로 추상적인 데이터 타입을 의미한다. 데이터 타입이란 우리가 잘 알 듯이 int x; char y;할 때의 int, char가 데이터 타입이다. 컴파일러에서 제공하는 데이터 타입은 이 외에도 double, float, struct 등 여러 가지가 있을 수 있다. 그러나 이러한 기본적이고 원초적인(Primitive) 데이터 타입만으로 프로그램을 짠다는 것은 매우 어려운 일이다.

우리가 어떤 문제를 해결할 때에는 추상화를 생각해 볼 수 있다. 집을 설계하는데 있어서 못, 볼트, 너트, 2mm 유리 등의 기본적인 단위로 설계하는 것보다는 추상적으로 화장실, 거실, 침실이라는 단위로 설계하는 것이 훨씬 용이함은 두말할 나위가 없다. 마찬가지로 프로그램에서도 우리가 필요로 하는 데이터 타입을 추상적으로 정의하고 사용할 필요가 있다. 예를 들어, 누가 성적관리 프로그램을 짠다면 당연히 학과목별로 성적을 나열하고 보여주는 것이 필요하다는 추상적 기능을 먼저 생각할 것이다.



[그림 2-1] 집 설계와 추상화


기본적인 데이터 타입을 기준으로 생각한다면 일은 어려워진다. 예를 들어, 학번을 int 타입으로 잡고, 학생명을 최대 4글자인 배열로 잡을까부터 생각하는 경우다. 다시 말해, 볼트 너트 단위로 집을 설계하는 셈이다. 이에 반해 추상화는 필요한 작업을 생각하는데에서 출발한다. 뭔지 잘 모르지만 어떤 데이터를 나열하고 보여줄 수 있는 것? 이걸 추상적으로 표현하면 ‘리스트(List)’ 정도가 좋을 것이다. 만약 이 추상 자료형 ‘리스트’를 정의하면 이후에는 이 리스트를 기본적인 데이터 타입으로 간주하고 프로그램을 짜 나갈 수 있다.

그렇다면 추상 자료형을 정의하는데 필요한 것은 무엇인가. 위 예에서 보는 바와 같이 ‘리스트’이라는 추상적인 단어를 끄집어 내는 데에는 단지, ‘어떤 데이터를 나열하고 보여줄 수 있는 것’이라는 생각만으로 충분하다. 다시말해, 내가 필요한 작업(Operation)만 떠올리면 충분하다. 여기서 중요한 것은 추상 자료형을 선언하는 사용자로서는 구체적으로 어떤 방법으로 그 작업이 이루어지는지는 알 바가 아니고, 알고 싶지도 않다는 것이다. 다시 말해, 해당 작업을 어떻게 구현하는가는 전적으로 구현자 책임이다. 이처럼 사용자와 구현자 사이에 뚜렷한 책임의 한계를 설정한 것이 추상 자료형이다.

또 다른 예를 들어보자. “구체적으로 어떻게 만들어야 할지는 모르겠지만, 버튼만 누르면 냉수도 나오고, 부순 얼음도 나오고, 각진 얼음도 나오는 뭐 그런 놈이 하나 있으면 좋겠다. 나는 이런 일 할 수 있는 놈을 얼음 제조기라고 불러야겠다.”라고 말하면 이미 추상 자료형 “얼음 제조기(IceDispenser)”를 정의한 것이다.



[그림 2-2] 얼음 제조기 추상화


[코드 2-1: 얼음 제조기]

ADT IceDispenser
- Operation
      : GetMeChilledWater( ); 냉수 주시요.
      : GetMeCrushedIce( ); 부순 얼음 주시요.
      : GetMeCubeIce( ); 각진 얼음 주시요.





[그림 2-3] 추상 자료형 얼음 제조기


대표적인 추상화의 속성으로서 일반성(Generality)을 들 수 있다. 작업을 정의하는데 있어서 지엽적인 세부사항(Detail)은 무시된다는 것이다. 즉, 일반적으로 필요할 것같은 작업만 정의하지 어느 한 사람이 필요로 하는 특정 목적의 작업은 정의하지 않는다는 것이다. 예를 들어, 도넛 모양의 얼음을 달라든지, 별 모양 얼음을 달라든지 하는 것은 그리 일반적이지 않다는 것이다. 이는 어떤 특정한 목적을 배제함으로써 해당 작업의 범용성(General Usability)를 높이기 위한 것이다. 비유적으로 말하면 맞춤복은 어느 한 사람에게만 맞지만, 표준 체형을 감안한 기성복은 여러 사람에게 맞을 수 있기 때문이다.

이처럼 추상적인 또는 개념적인 데이터 타입을 정의함으로써 프로그래머는 코드 작성이 용이해진다. 사용자 관점(User View, Interface View)에서는 필요한 무엇인가 있으면 추상적인 수준에서 어떤 어떤 작업을 할 수 있다고 가정하고 코딩을 계속하면 된다. 구체적으로 그것이 어떻게 구현되어야 하는지에 대해서는 일체 생각하지 말고 일단 그런 것이 있다고 가정하고 그 위에서 코딩할 수 있다는 의미다. 크고 복잡한 문제를 공략하기 위해서는 당연히 이처럼 추상화 수준을 높이는 것이 유리하다.

3. 구현자 관점

프로그래머로서 이 책의 독자는 사용자 관점과 구현자 관점을 모두 알아야 한다. 사용자 입장에서 타인의 추상 자료형을 가져다 쓰기도 하지만, 반대로 타인을 위해 자신이 책임진 추상 자료형을 구현할 수도 있어야 하기 때문이다.

얼음 제조기를 가져다 쓰는 사람은 작업 명령만 내리면 된다. 그러나, 이 얼음 제조기를 만들어야(Implementation) 하는 사람의 입장에서는 냉수 만들기, 부순 얼음 만들기, 각진 얼음 만들기 등의 작업을 가능케 하는 구체적인 매커니즘을 실제로 구현해야 한다. 뿐만 아니라, 이를 작업을 수행할 수 있는 재료나 도구도 필요하다.

구현자 관점(Implementation View)에서 추상 자료형은 작업을 수행하기 위한 구체적인 방법과 그 방법을 적용하기 위한 데이터 집합을 말한다. 추상 자료형은 문자 그대로 해석하면 추상 데이터 타입이다. 그러나 구현자 관점에서 이 데이터 타입은 정수, 문자, 부동소수 등 일반적인 데이터 타입과는 달리, 데이터 집합과 그 데이터에 가해지는 작업을 묶은 것을 말한다. 추상 자료형은 작업을 기준으로 정의된 데이터 타입이므로 작업과 그 작업이 가해지는 데이터를 분리해서 생각할 수는 없기 때문이다. 구현자 관점에서 추상 자료형은 데이터 집합과 그 위에 가해지는 작업을 합친 것으로 정의된다. 얼음 제조기를 예로 들면 다음과 같다.

[코드 2-2: 얼음 제조기]

ADT IceDispenser
- Data
      : Water, Motor, Button  물, 모터, 버튼
- Operation
      : GetMeChilledWater( ) 냉수 주시요의 구체적 구현
        {
        }
      : GetMeCrushedIce( ) 부순 얼음 주시요의 구체적 구현
        {
        }
      : GetMeCubeIce( ) 각진 얼음 주시요의 구체적 구현
        {
        }


구현자 입장에서는 데이터와 그 데이터를 처리하는 작업을 분리해서 생각해선 안된다. 예를 들어, 공이라는 데이터가 던지고 차고 하는 작업에 사용되고, 종이라는 데이터가 읽고 쓰고 하는 작업에 사용된다면, 공은 던지고 차기 좋게 만들어져야 하고, 종이는 읽고 쓰기 좋게 만들어져야 한다. 다시 말해, 데이터는 그 데이터를 가공할 작업을 전제로 해서 정확히 그 목적에 알맞도록 설계되어야 한다.

‘자료구조’는 이처럼 작업이 가장 효율적으로 수행될 수 있도록 조직화된 데이터 집합을 말한다. 비유적으로 말하자면 물, 모터, 버튼 등의 데이터 집합을 하나의 구조체(Structure)로 가져갈지 아니면 배열(Array)로 가져갈지가 바로 자료구조의 문제라 할 수 있다. 물론, 추상 자료형을 정의하고 사용하는 사람의 입장에서는 자료구조에는 관심이 없다. 데이터를 어떻게 조직화할지는 구현과 관련된 일이기 때문이다. 사용자 관점의 추상 자료형과 구현자 관점의 추상 자료형을 요약하면 [표 2-1]과 같다.



[표 2-1] 추상 자료형에 대한 관점의 차이


Ⅱ. 추상 자료형과 C

추상 자료형 개념은 어떤 특정 언어에만 적용되는 것이 아니다. 어떤 문제를 풀기 위해 프로그래머가 사용해야 하는 일종의 방법론이다. 객체지향 언어가 아닌, 기존 C 언어에서 추상 자료형 개념은 사용자 파일과 구현자 파일을 별도로 나누는 형태로 나타난다. 확장자 .h로 끝나는 헤더(Header) 파일은 사용자의 관점의 인터페이스 파일이며, 확장자 .c 파일은 구현자 관점의 구현 파일이다. 예를 들어, C의 헤더 파일 IceDispense.h에는 다음과 같은 내용을 넣을 수 있다.

[코드 2-3: C로 구현한 얼음 제조기]

typedef struct 
{  int Water;            물의 양
    int Motor;             모터 회전수
    int Button;            버튼 1, 2, 3
} materialType;

 void GetMeChilledWater( );     냉수 주시요
 void GetMeCrushedIce( );       부순 얼음 주시요
 void GetMeCubeIce( );            각진 얼음 주시요


이 인터페이스 파일은 추상 자료형을 사용하는 사람과 구현하는 사람 사이에 일종의 계약서와 같은 것이다. 따라서 이 계약서는 계약자 간의 오해를 방지할 수 있게 자세할수록 좋다. 다시 말해 헤더 파일의 커멘트는 그 커멘트 내용만을 읽고서도 사용하는 사람이 정확하게 각 작업이 무엇을 하기 위한 것인지 알 수 있도록 자세하고도 정확하게 서술되어야 한다.

외부 사용자는 이 헤더 파일만 읽고 필요한 작업을 불러서 사용하면 된다. 각 작업을 구체적으로 구현한 “IceDispenser.c" 파일은 볼 필요가 없다. 구현방법이 사용자 눈에 보이지 않으므로 이는 정보의 은닉(Information Hiding)에 해당한다. 만약 추상 자료형을 사용하지 않는다면 사용자 코드와 구현자 코드가 서로 섞여서 작성될 수도 있다. 예를 들어, 사용자 코드가 구현자 코드의 어떤 변수를 가져다 읽을 수도 있다. 그러나 추상 자료형에서는 구현자 코드가 일체 보이지 않는다. 단지 블랙박스로서의 구현자 코드가 있을 뿐이다. 따라서 사용자 코드는 이 블랙박스에게 함수명을 대고 그 결과를 받아갈 수 있을 뿐이다.

구현방법이 숨겨져 있으므로 사용자로서는 자신의 코드가 구체적 구현방법을 기준으로 할 수 없게 된다. 따라서 사용자는 단지 계약서인 헤더 파일에 나타난 작업명(Function Prototype)만을 기준으로 짜게 된다. 결과적으로 블랙박스 내부의 구현방법이 바뀌어도 불러서 쓴 사람의 코드가 바뀔 필요는 없게 된다. 이는 코드의 단위성(Modularity)을 높이는 가장 효율적인 방법이다. 하나의 코드가 바뀌면 그 코드를 사용하는 다른 여러 가지 코드가 바뀐다는 것은 프로그램 유지보수에 있어서 가장 지루하고 힘든 작업 중 하나이기 때문이다.

그러나 C는 절차적 언어(Procedural Language)이므로 하나의 독자적인 명칭을 가진 추상 자료형의 존재를 인정하지 않는다. 위를 예로 들면, 얼음 제조기라는 추상적인 객체를 인정하지 않은 채 단지, 필요한 작업명과 그 작업이 가해지는 데이터 타입을 헤더 파일에 명시하는데 그친다. 따라서 C에 의한 추상 자료형의 구현은 그 추상 자료형의 이름 즉, 존재가 드러나지 않는다. 언어의 특성상 그러한 추상 자료형을 정의할 여지가 없는 것이다. 반면에 이러한 추상적인 데이터 타입의 존재를 선언할 수 있게 하는 것이 객체지향 언어다.


Ⅲ. 추상 자료형과 C++

사용과 구현의 분리, 작업을 기준으로 정의된 데이터 타입 등 추상 자료형의 개념은 객체지향 개념과 정확히 일치한다. 추상 자료형 선언은 객체 지향의 클래스 선언에 해당한다. 또, 추상 자료형에 선언된 작업은 객체지향 클래스에 선언된 메시지에 해당한다. 추상 자료형에서 작업의 구현이 외부로부터 숨겨진 것과 마찬가지로, 객체지향에서 메시지의 구현도 외부로부터 숨져져 있다.

이는 정보의 은닉(Information Hiding)에 해당한다. 얼음 제조기를 사용하는 사람은 구체적으로 그 부순 얼음이 어떻게 만들어졌는지 알 필요가 없다. 외부 사용자가 얼음 제조기 내부를 들여다 볼 필요도 없고 또 들여다 봐서도 안된다. 모터를 돌려서 갈아내든 아니면 기계 뒤에서 서비스맨이 망치로 부수든 간에 하여간 부순 얼음만 나오면 되었지 구체적인 구현방법은 알 필요가 없다. 또, 외부 사용자 입장에서는 어떤 재료 즉, 어떤 자료구조를 사용하는지 조차도 알 필요가 없다. 단지 원하는 버튼을 눌러서 얼음제조기 객체(Object)에게 메시지(Message)를 전달하기만 하면 된다.

객체지향에서 인캡슐레이션(Encapsulation)을 말할 때는 두 가지 의미를 포함한다. 첫째 의미의 인캡슐레이션은 [그림 2-4]처럼 캡슐(Capsule, Package) 밖에서 안을 들여다 볼 수 없다는 의미이다. 외부와 내부 사이에는 캡슐 벽(Wall, Barrier)이 가로막고 있기 때문에 객체 밖의 사용자가 객체 안의 구현방법이나 자료구조를 알 수 없다. 캡슐의 외부와 내부 사이에 교신을 위해 열린 유일한 창구는 메시지 전달을 위한 창구뿐이고, 다른 곳을 통해서 내부를 들여다 보려면 벽에 부딪칠 뿐이다. 이는 정보의 은닉을 의미한다.




[그림 2-4] 정보 은닉과 인캡슐레이션


둘째 의미의 인캡슐레이션은 하나 이상의 요소를 캡슐이 둘러싸고 있다는 의미다. 즉, 작업 구현방법과 자료구조라는 두 가지를 묶어서(Coupling) 하나의 캡슐안에 집어 넣었다는 의미다. 이처럼 두 개를 묶는 이유는 작업을 떠나서 데이터를 생각할 수는 없기 때문이다.

둘째 의미에서 C와 C++는 차이가 있다. 물론 C 언어의 헤더 파일에 작업과 해당 데이터 타입이 같이 들어가 있기는 하지만 그냥 같이 들어가 있는 정도다. 일종의 느슨한 커플링(Loose Coupling)이다. 객체를 선언할 수 없기 때문에 이 두 가지가 어떤 객체에 소속되어 있는지가 전혀 나타나지 않기 때문이다. 반면 C++은 객체 선언을 허용하므로 객체 클래스 정의에서 괄호를 사용하여 작업과 데이터라는 두 가지 요소를 단단히 묶어버린다(Tight Coupling).

일반적으로 인캡슐레이션을 지원하는 언어의 소스 파일은 여러 개로 구성된다. 인캡슐레이션은 프로그램 언어 설계(Language Design)의 문제다. 즉, 사용하는 프로그램 언어에 따라서 이 개념이 지원되기도 하고 그렇지 않기도 한다. 다시 말해, 어떤 언어가 추상화 내부에 감춰진 정보를 보지 못하게 하는 기능이 있을 때만 효율적으로 인캡슐레이션이 이루어진다. ADA 패키지(ADA Package)라든지, C++ 클래스(C++ Class), 그리고 제한적이나마 C 모듈(C Module)이 인캡슐레이션 개념을 지원한다.

정보의 은닉은 프로그램 설계(Program Design)의 문제다. 정보의 은닉은 대부분의 언어가 지원하는 기능으로서 이 경우의 정보의 은닉은 타인이 내 소스 파일을 이용만 하고 보지 못하게 하는 단순한 차원의 은닉을 말한다. 추상화, 정보의 은닉, 인캡슐레이션은 서로 다른 개념이지만 연관되어 있다. 추상화에 의해 사용자는 중요한 것에만 집중할 수 있다. 정보 은닉은 중요하지 않는 것을 숨기는 것이다. 인캡슐레이션은 중요하지 않은 것을 숨기는 기술이다.

추상 자료형 “얼음 제조기”를 C++로 표시하면 다음과 같다:

[코드 2-4: C++로 구현한 얼음 제조기]

class IceDispenser 클래스 얼음 제조기

{ public:
      void GetMeChilledWater( ); 냉수 주시요
      void GetMeCrushedIce( ); 부순 얼음 주시요
      void GetMeCubeIce( ); 각진 얼음 주시요
  private:
      int Water, Motor, Button; 메시지 실행을 위한 데이터 집합
 }


추상 자료형의 자료구조는 C++의 멤버 데이터에 해당한다. 또, 추상 자료형의 작업은 C++ 멤버 함수에 해당한다. 예를 들어, 멤버 함수인 GetMeCrushedIce 함수는 멤버 데이터인 Water를 재료로 이 물이 얼음으로 바뀌도록 상태 변화를 유도한다고 볼 수 있다. 이처럼 멤버 함수는 멤버 데이터의 상태를 바꿔가면서 자신이 원하는 기능을 수행한다. 객체지향, 추상 자료형, C++에서 사용되는 유사한 용어를 통합하여 정리하면 [표 2-2]와 같다.



[표 2-2] ADT = Operation + Collection of Data
TAG :
댓글 입력
자료실

최근 본 책0