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

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

by CHML 2016. 4. 24.

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


1. 데이터 클래스의 확장

앞의 글에서 데이터 클래스에 직접 파일 연산을 구현하는 것과 버퍼 클래스를 이용하는 것의 문제점을 서술하였다. 클래스에 직접 파일 연산을 구현하는 방식의 단점을 극복하기 위해 버퍼 클래스를 따로 구현하였지만, 버퍼 클래스를 이용하는 방식 또한 메인 함수에 데이터 클래스의 pack, unpack 과정이 모두 드러나는 문제점이 존재하였다.

버퍼 클래스를 이용하면서 메인 함수를 간단하게 만들기 위한 방법은 매우 간단하다. 바로 버퍼 클래스의 pack, unpack 함수를 호출하는 연산을 데이터 클래스 내부에 구현하는 것이다. 예를 들어, 데이터 클래스 Person을 아래와 같이 수정하면 메인 함수는 매우 간단해질 것이다.

데이터 클래스의 pack과 unpack 함수는 인자로 전달받은 버퍼 클래스의 pack과 unpack 함수를 반복적으로 호출하는 함수이다. 데이터 클래스 Person은 id, name, age, job로 구성된 총 4개의 필드를 갖기 때문에 Person 클래스의 pack과 unpack은 아래와 같이 버퍼 클래스의 pack, unpack을 4번 호출한다. Person 클래스의 id와 age는 정수형 데이터이지만, 아래의 코드에서는 문자열로 변환하였다고 가정한다.

버퍼 클래스의 pack과 unpack 함수는 성공적으로 기능을 수행하면 TRUE를 반환한다. 따라서, 위의 코드처럼 Person 클래스의 pack과 unpack 함수를 구현하면 Person 클래스의 pack과 unpack 함수 또한 모든 필드에 대해 성공적으로 기능을 수행하면 TRUE를 반환한다. 데이터 클래스 내부에 버퍼 클래스의 pack과 unpack을 반복적으로 호출하는 데이터 클래스의 pack, unpack 함수를 구현하면 메인 함수는 아래와 같이 매우 간단해진다.

Person 클래스의 정의에서 드러나듯이 각각의 버퍼 클래스에 대해 pack과 unpack 함수를 데이터 클래스 내부에 정의해야 하기 때문에 버퍼 클래스의 수 X 2개의 함수를 데이터 클래스에 구현해야 한다. 즉, 데이터 클래스의 내용이 길어지고 새로운 버퍼 클래스가 추가될 때마다 데이터 클래스의 내용을 수정해야하는 문제점이 발생하는 것이다.


2. 추상화와 클래스 계층구조를 이용한 구현

데이터 클래스에 pack과 unpack 함수를 구현 방식은 버퍼 클래스가 추가될 때마다 데이터 클래스에 새로운 pack과 unpack 함수를 추가해야하는 문제점이 존재하였다. 이러한 문제점을 해결하기 위한 방법으로는 추상화(abstraction)와 클래스 계층구조(class hierarchy)를 이용하는 것이 있다. 추상화는 개별 사물에 종속적이지 않은 공통된 특징을 추출하는 것이다. 예를 들어, 고양이는 고양이마다 모두 특징이 모두 다르지만 귀와 꼬리가 있고, 다리가 4개라는 것과 같이 모든 고양이가 갖고 있는 특성을 추출하는 것이 바로 추상화이다.

고정 길이 필드 버퍼, 길이 지시자 버퍼, 구획 문자 버퍼 등 모든 버퍼 클래스는 문자형 배열(char array)을 버퍼 클래스 내부에 포함하여 파일 입출력에 이용하고, write/read와 pack/unpack 연산을 제공한다. 따라서, 버퍼 클래스가 갖는 공통된 특성을 추출하여 IOBuffer 라는 모든 버퍼 클래스에 대한 추상 클래스를 정의할 수 있다.

또한 버퍼 클래스는 고정 길이 레코드와 가변 길이 레코드를 이용하는지에 따라 두 가지로 분류할 수 있으며, 레코드에 대한 방식을 결정하면 다시 필드에 대한 방식에 따라 버퍼 클래스를 나눌 수 있다. IOBuffer로 시작하여 레코드에 따라, 그다음 필드에 따라 클래스를 위에서 아래로 세분화한 것을 클래스 계층구조라고 한다. 버퍼 클래스의 클래스 계층구조를 트리의 형태로 나타내면 아래와 같다. 계층구조를 설계할 때는 정답이 있는 것이 아니다. 아래의 계층구조 또한 하나의 예시일 뿐이다.



위와 같은 버퍼 클래스의 추상화 및 계층구조 설계를 하였다면, 데이터 클래스의 pack과 unpack 함수는 IOBuffer에 대해 단 하나씩만 구현하면 된다. 데이터 클래스 내부에 정의되는 IOBuffer에 대한 pack과 unpack 함수의 구현은 아래와 같다.

IOBuffer의 pack과 unpack 함수는 virtual로 선언되었기 때문에 런타임(run-time)에서 동적 바인딩(dynamic binding)된다. 따라서, 데이터 클래스 Person의 pack과 unpack은 IOBuffer를 상속받는 버퍼의 종류에 따라 버퍼 클래스의 pack, unpack 함수를 선택적으로 호출할 수 있게 된다. 추상화와 클래스 계층 구조를 이용하면 Person 클래스의 명세를 아래와 같이 간단하게 바꿀 수 있다. 새로운 레코드 및 필드 저장 방식을 위해 버퍼 클래스를 추가하고자 하는 경우에도 데이터 클래스를 수정하는 것이 아니라, 새로운 버퍼 클래스를 IOBuffer의 명세에만 맞게 정의하면 된다.