자료구조를 다룰때 많이 사용되는 Iterator 패턴을 살펴보도록 하겠습니다.
보시다시피 8bit 자료형을 취급하는 iterator입니다. setup_iterator은 커스텀되어있는 자료형과 연결하는 함수입니다. 즉, 사용자가 만든 자료형과 인터페이스와 연결하는 부분입니다.
/* iterator.h */
typedef struct
{
void (*init)(void);
bool (*set_value)(uint8_t value);
bool (*get_first_value)(uint8_t re_value);
bool (*get_next_value)(uint8_t re_value);
bool (*has_next)(void);
uint8_t *(*get_current_buffer)(void);
}iterator_t;
extern int8_t setup_iterator(iterator_t const *iterator);
extern bool set_iterator_value(int8_t id, int8_t value);
extern bool get_iterator_first_value(int8_t id, int8_t *re_value);
extern bool get_iterator_next_value(int8_t id, int8_t *re_value);
extern bool has_iterator_next(int8_t id);
extern bool get_iterator_current_buffer(int8_t id, uint8_t *re_value);
아래 코드에서 위에서 만든 인터페이스를 핸들링하는 코드들 만들어 보도록 하겠습니다. 최대 지원되는 iterator은 10개로 지정했습니다(사용자는 맘 대로 바꿀 수 있습니다). 즉, 10개의 8bit 자료구조형과 연결될 수 있습니다.
이렇게 C에서는 갯수를 제한해야 합니다. 아쉽게도 Java의 new 처럼 자유롭게 생성하지 못하고 일일히 만들어 줘야합니다. 이런점에서는 Java보다 C의 생산성이 많이 떨어집니다.
/* iterator.c */
#define MAX_ITERATOR_COUNT (10)
static const iterator_t *iterator_array[MAX_ITERATOR_COUNT] = {null};
static int8_t iterator_total = 0;
int8_t setup_iterator(iterator_t const *iterator)
{
int8_t result = -1;
if(iterator_total < MAX_ITERATOR_COUNT)
{
iterator_array[iterator_total] = iterator;
iterator->init();
result = iterator_total;
iterator_total++;
}
return result;
}
bool set_iterator_value(int8_t id, int8_t value)
{
if((id < 0) || (id >= iterator_total) || (iterator_array[id] == null))
{
return false;
}
return iterator_array[id]->set_value(value);
}
bool get_iterator_first_value(int8_t id, int8_t *re_value)
{
if((id < 0) || (id >= iterator_total) || (iterator_array[id] == null))
{
return false;
}
return iterator_array[id]->get_first_value(re_value);
}
bool get_iterator_next_value(int8_t id, int8_t *re_value)
{
if((id < 0) || (id >= iterator_total) || (iterator_array[id] == null))
{
return false;
}
return iterator_array[id]->get_next_value(re_value);
}
bool has_iterator_next(int8_t id)
{
if((id < 0) || (id >= iterator_total) || (iterator_array[id] == null))
{
return false;
}
return iterator_array[id]->has_next();
}
int8_t *get_iterator_current_buffer(int8_t id, uint8_t *re_value)
{
if((id < 0) || (id >= iterator_total) || (iterator_array[id] == null))
{
return false;
}
re_value = iterator_array[id]->get_current_buffer();
return true;
}
사용자가 만든 자료형과 위의 함수를 연결해 주고 각각 호출되는 함수를 Java의 인터페이스처럼 구현해 주면 됩니다. 이번 예제에서는 8bit circle queue 자료형을 만들어서 연결시켜보도록 하겠습니다.
먼저 헤더입니다. Java로 보면 하나의 instance가 될 부분입니다. 하나의 프로그램에서 생성된 하나의 instance가 고유하듯 C에서도 고유하게 ID를 명시적으로 썼습니다.
/* queue.h */
extern int8_t QUEUE_8BIT_ID;
extern bool init_queue_8bit(void);
이 instance의 Concret code는 아래와 같습니다. 데이터를 다루는 방법에 따라 자료형의 구조가 다르기 때문에 개발자는 특성에 맞게 만드셔야 합니다. 여기서는 queue이기 때문에 그에 맞게 구현되어 있습니다.
init_queue_8bit() 함수는 iterator interface와 연결하는 부분입니다.
/* queue.c*/
int8_t QUEUE_8BIT_ID = -1;
static void init(void);
static bool set_value(int8_t value);
static bool get_first_value(uint8_t re_value);
static bool get_next_value(uint8_t re_value);
static bool has_next(void);
static uint8_t *get_current_buffer(void);
static const iterator_t this =
{
.init = init,
.set_value = set_value,
.get_first_value = get_first_value,
.get_next_value = get_next_value,
.has_next = has_next,
.get_current_buffer = get_current_buffer
};
bool init_queue_8bit(void)
{
QUEUE_8BIT_ID = setup_iterator(&this);
if(QUEUE_8BIT_ID == -1)
{
return false;
}
else
{
return true;
}
}
#define MAX_BUFFER_SIZE (100)
static int8_t buffer[MAX_BUFFER_SIZE];
static int8_t head;
static int8_t tail;
static void init(void)
{
head = 0;
tail = 0;
}
static bool set_value(int8_t value)
{
buffer[tail++] = value;
if(tail >= MAX_BUFFER_SIZE)
tail = 0;
return true;
}
static bool get_first_value(uint8_t *re_value)
{
return false;
}
static bool get_next_value(uint8_t *re_value)
{
int8_t value;
if(head == tail)
{
return false;
}
value = buffer[head++];
if(head >= MAX_BUFFER_SIZE)
{
head = 0;
}
*re_value = value;
return true;
}
static bool has_next(void)
{
if(head == tail)
{
return false;
}
else
{
return true;
}
}
static uint8_t *get_current_buffer(void)
{
return &buffer[tail];
}
이렇게 구현하면 이 8bit queue에 접근하기 위해서는 QUEUE_8BIT_ID를 가지고 iterator.c의 함수를 이용하면됩니다. 위와 같이 구현하면 어떠한 자료형도 같은 메소드(함수)를 통해 접근이 가능해 집니다. 그러나 내부 구현된 방식은 각각 특성에 맞게 달라지게 됩니다.
위의 예제는 8bit 자료형으로 구현했지만, void, int, 등도 가능하며 queue 뿐만 아니라 stack, linked list 도 구현만 다들 뿐 같은 intefacing이 가능합니다.
감사합니다.
'▶ C Application > 디자인 패턴' 카테고리의 다른 글
소프트웨어 Timer 만들기 (0) | 2023.12.20 |
---|---|
Template method 패턴 (0) | 2023.12.17 |
Singleton 패턴 (0) | 2023.12.16 |
Observer 패턴 (0) | 2023.12.14 |
Strategy 패턴 (0) | 2023.12.12 |