본문 바로가기
객체지향설계

[파일구조] - 파일구조 설계 III

by CHML 2016. 4. 24.

이 글의 소스 코드는 File Structures: An Object-Oriented Approach with C++ 3rd Edition의 내용을 수정하여 작성한 것 입니다.


1. 파일에 대한 해석

지금까지 데이터 클래스 내부에 연산을 구현하는 방식부터 추상화(abstraction)와 클래스 계층구조(class hierarchy)를 이용하는 방식까지 다양한 방식으로 파일구조를 설계하였다. 설계에서 가장 중요한 점은 설계의 목적을 잊지 않는 것이다. 파일구조를 설계하는 목적은 결국 파일에 데이터를 저장하고 읽기 위함이다. 따라서, 파일이라는 것에 대한 명확한 정의를 내릴 필요가 있는데, 앞의 글에서 설명한 바와 같이 파일의 정의는 파일구조의 설계에 따라 달라진다. 지금까지 구현한 방식에 따라 파일은 아래와 같이 두 가지로 정의될 수 있다.


  • 데이터 클래스 내부에 연산 구현: 파일은 바이트의 연속이다.
  • 버퍼 클래스를 이용한 구현: 파일은 레코드의 집합이다.
파일에 대한 이러한 정의는 시스템 프로그래머나 데이터베이스 관리자의 입장에서는 매우 직관적인 표현이다. 하지만 일반 이용자나 프로그래머의 관점에서 볼 때 파일은 단순히 데이터의 집합이다. 따라서, 데이터 클래스 단위로 파일 입출력을 구현하는 것이 바이트 스트림이나 레코드 단위로 파일 입출력을 구현하는 것보다  한 단계 더 추상화된 설계라고 할 수 있다. 이러한 구현을 위해 새롭게 정의하는 것이 BufferFile과 RecordFile이다.


2. BufferFile

파일을 데이터의 집합으로 설계하면서 추상화 및 클래스 계층구조를 이용한 구현의 이점을 모두 취하기 위해서는 간단하게 파일 자체에서 IOBuffer를 제공하도록 구현하면 된다. 파일과 IOBuffer를 결합한 객체가 바로 BufferFile이며, BufferFile의 명세는 아래와 같다.

BufferFile은 멤버 변수로 파일과 IOBuffer를 가지며, 파일 연산으로 open, create, close, read, write를 갖는다. BufferFile의 read 함수는 아래와 같이 간단하게 구현된다.

int BufferFile:read() { return buffer.read(file); }

또한, BufferFile은 main 함수에서 아래와 같이 이용할 수 있다.

위의 코드를 보면 알 수 있듯이 BufferFile 또한 여러 문제점이 존재한다. BufferFile의 문제점은 크게 아래와 같이 세 가지가 존재한다.


  • BufferFile의 생성자에 어떤 버퍼 클래스를 전달해야 하는지 코드상에 서술해야 한다. 즉, 파일 자체에 저장 형식이 명시되어 있지 않다.
  • main 함수에 데이터를 pack, unpack 하는 과정이 모두 드러나게 된다.
  • 파일 자체에 파일과 버퍼 클래스가 포함되어 있지만, 데이터 단위가 아닌 레코드 단위로 파일 연산을 수행한다.


2. RecordFile

RecordFile은 BufferFile과 대부분이 동일하지만 클래스 단위로 파일 입출력을 수행한다는 점이 다르다. 클래스 단위로 파일 입출력을 수행한다는 의미는 아래의 코드를 보면 알 수 있다.

RecordFile과 BufferFile의 다른점은 템플릿(template)를 이용하여 데이터 클래스의 타입을 전달받는 것이다. 템플릿을 이용하여 이질적인 데이터 클래스를 모두 동일한 코드로 처리하기 위해서는 RecordFile의 명세와 파일 입출력 연산을 아래와 같이 정의할 필요가 있다.

RecordFile은 BufferFile을 상속받는다. RecordFile과 BufferFile의 다른점은 RecordFile의 read, write 함수는 내부에 BufferFile의 read, write를 호출하여 파일을 버퍼에 읽고 쓰는 것뿐만 아니라 템플릿 구문을 통해 전달받은 데이터 클래스의 unpack과 pack도 같이 호출한다는 것이다. 템플릿 구문과 BufferFile 상속 그리고 read, write 함수의 구현을 통해 RecordFile을 아래와 같이 한 줄의 코드만으로 파일에서 클래스 단위의 버퍼 입출력을 수행할 수 있게 된다.

personFile.read(person);

그러나 RecordFile 또한 BufferFile이 갖고 있는 '어떤 버퍼 클래스를 이용하여 파일을 해석해야 하는지'를 코드상에 포함해야 한다는 문제점을 해결할 수 없다. 이러한 문제점을 해결하기 위한 방법 중 하나로는 헤더 레코드(header record)가 있다.


3. 헤더 레코드(Header Record)

헤더 레코드의 목적은 파일 자체에 파일을 해석하기 위한 방법을 제공하는 것이다. 일반적으로 헤더 레코드는 파일의 시작에 위치하며 레코드의 수, 레코드의 길이 등에 대한 정보를 포함한다. 헤더 레코드를 파일에 구현함으로써 소프트웨어는 헤더 레코드를 읽어서 해석하는 기능만 구현된다면 헤더 레코드를 포함하고 있는 모든 종류의 파일을 읽을 수 있게 된다(파일을 읽는 것과 파일을 해석하여 출력하는 것은 다르다). 아래와 같이 IOBuffer에 virtual 키워드를 이용하여 헤더 레코드를 읽고 쓰는 함수를 선언하고 각 버퍼 클래스에 구현하면, RecordFile을 그대로 이용하면서 헤더 레코드를 해석할 수 있게 된다.


4. RecordFile의 구조와 RecordFile::read 함수의 동작

지금까지 설계한 RecordFile의 구조와 파일 연산의 과정을 도식화하면 아래와 같다.