Builder 패턴 - 키(Key)처리

이번 포스팅에서는 각각의 구현은 다르지만 기능은 같은 객체들을 관리하는 Builder 패턴에 대해 알아보겠습니다.

 

간단하게 예를 들면 사용자측 코드 즉, main 함수에서는 객체를 만들 뿐 기능을 사용하기 위해 기능 함수를 직접 호출하지 않고 관리자를 통해서만 일 처리를 합니다.

Key(버튼) 처리하는 예제로 Builder 패턴을 상세히 들여다 보겠습니다. Key를 입력받아 처리하는 식의 기능은 firmware에서는 참 많이 쓰입니다.

Key 처리 기능은 하드웨어적으로 참 많이 바뀝니다. 예를 들어 단순 1:1 GPIO를 사용할 수도 있고 ADC를 사용할수도 있으며 혹은 1:N 매트릭스 GPIO를 사용할 수도 있습니다. 아니면 세가지 다 사용할 수도 있겠지요.

주의할 점은 어떠한 하드웨어 Key를 사용하더라도 이용하는 사용자 측면에서는 코드의 변화가 없어야 합니다. 그래야 유지보수 및 확장성이 용이합니다.

먼저 헤더에 Key interface 와 관리자 함수를 만들어 보도록 하겠습니다. C언어에서는 제가 늘 강조하듯 interface가 없기 때문에 아래처럼 structure를 사용합니다. 관리자 함수는 setup_key(key_t *key) 하나입니다. Key는 총 3(A,B,C)개를 받게 되어 있는데, 이는 필요에 따라서 늘리면 됩니다.

 
/* key.h */
typedef enum
{
	KEY_NONE = -1,
	KEY_A = 0,
	KEY_B,
	KEY_C,
	KEY_MAX,
}key_name_t;

typedef struct
{
	void (*init)(void);
	void (*set_key)(key_name_t name);
	key_name_t (*get_key_pushed)(void);
}key_t;

extern bool setup_key(key_t *key);
 

Key interface인 key_t자료형은 각각의 Key 객체에서 구체적으로 구현해야 합니다. 이번 예제에서는 GPIO, ADC 두 가지 객체를 만들어 보도록 하겠습니다. 먼저 ADC Key 객체 헤더입니다.

/* key_adc.h */
extern key_t *setup_key_adc(void);
 

한 줄입니다. main에서 이 함수를 호출해서 key_t 포인터를 key 관리자 함수 setup_key에 넘겨주면 됩니다.

 

이번엔 ADC Key 객체 구현 코드를 살펴보겠습니다. setup_key_adc를 호출하면 ADC key 객체의 포인터를 넘겨줍니다. 그럼 Key 관리자는 지속적으로 어느 키가 눌렸는지 get_key_pushed를 통해서 지속적으로 체크 합니다.

/* key_adc.c */
static void init(void);
static void set_key(key_name_t name);
static key_name_t get_key_pushed(void);

static key_t this = {null};
static uint8_t key_names[KEY_MAX];
static uint8_t key_number = 0;

key_t *setup_key_adc(void)
{
	this.init = init;
	this.set_key = set_key;
	this.get_key_pushed = get_key_pushed;

	return &this;
}

static void init(void)
{
	set_key(KEY_A);
	set_key(KEY_B);	
	set_key(KEY_C);
}

static void set_key(key_name_t name)
{
	if(key_number >= KEY_MAX)
		return;

	key_names[key_number] = name;
	key_number++;
}

static key_name_t get_key_pushed(void)
{
	// need to implement with ADC

	return KEY_NONE;
}
 

get_key_phushed에 각각의 하드웨어에 맞는 구체적인 코드가 들어 가면 됩니다.

 

Key 관리자는 주기적으로 어느 키가 눌렸는지 체크해야 하는 데 이번 포스트에서 만든 소프트웨어 timer를 사용하도록 하겠습니다. 다음은 Key 관리자 코드 입니다. 먼저 등록된 Key 객체를 assign해 주고 init() 함수를 호출한 다음 timer를 하나 동작 시킵니다.

/* key_control.c */
static void scan(int8_t timer_id, uint32_t param1);
static key_t *this=null;

bool setup_key(key_t *key)
{
	this = key;
	this->init();

	if(register_timer(TIMER_50ms, scan) == -1)
		return false;

	return true;
}

static void scan(int8_t timer_id, uint32_t param1)
{
	key_name_t key_name_pushed = this->get_key_pushed();

	if(key_name_pushed != KEY_NONE)
	{
		set_message(MSG_TEST_1, (uint32_t)key_name_pushed, 0);
	}
}
 

관리자의 등록된 키를 50msec 주기로 체크하는 scan함수가 있습니다. scan(int8_t timer_id, uint32_t param1) 함수에서는 등록된 객체의 get_key_pushed 함수를 실행시켜 KEY_NONE이 아니면 message에 MSG_TEST_1과 눌린 Key의 명칭을 전달 하도록 하고 있습니다.

 

이제 main.c 의 infinite loop를 보도록 하겠습니다.

/* main.c */
void main(void)
{
	register_schedule(message_process);
	setup_key(setup_key_adc());

	while(1)
	{
		execute_schedule(0, 0);
	}
}

void message_process(uint32_t param1, param2)
{
	msg_t msg;
	uint32_t msg_param1, msg_param2;

	if(get_messge(&msg, &msg_param1, &msg_param2) == true)
	{
		switch(msg)
		{
		case MSG_NONE:
			break;
		case MSG_TEST_1:
			switch(param1)
			{
				case KEY_A:
					break;
				case KEY_B:
					break;
				case KEY_C:
					break;					
			}
			break;

		case MSG_TEST_2:
			break;
		case MSG_TEST_3:
			break;
		default:
			break;
		}
	}
}
 

마찬가지로 지난번 포스팅에서 만든 scheduler를 사용하고 있습니다. 메시지 process를 만들어 메시지를 받았을 때 처리하는 부분이 따로 분리되어 있습니다.

 

우선 setup_key 함수를 통해서 adc key 객체를 주면 끝입니다. 그럼 Key 관리자에서 주기적으로 Key를 체크해서 눌렸으며 메시지에 전달하고 메시지 process에서는 MSG_TEST_1의 눌린 키 정보에 따라 처리를 해주면 됩니다.

 

추가로 GPIO를 사용한 key 객체도 구현해 보도록 하겠습니다. adc와 거의 비슷한데 직접 GPIO를 체크하는 부분만 구현하면 됩니다. 참고로 50msec 마다 High/Low level 체크하기 때문에 채터링은 크게 신경쓰지 않아도 될 것 같습니다.

 

우선 GPIO key 객체의 헤더입니다.

/* key_gpio.h */
extern key_t *setup_key_gpio(void);
 

이제 본체를 보겠습니다. ADC의 객체와 거의 비슷하지만 get_key_pushed(void) 구현 부분만 달라집니다.

/* key_gpio.c */
static void init(void);
static void set_key(key_name_t name);
static key_name_t get_key_pushed(void);

static key_t this = {null};
static uint8_t key_names[KEY_MAX];
static uint8_t key_number = 0;

key_t *setup_key_gpio(void)
{
	this.init = init;
	this.set_key = set_key;
	this.get_key_pushed = get_key_pushed;

	return &this;
}

static void init(void)
{
	set_key(KEY_A);
	set_key(KEY_B);	
	set_key(KEY_C);
}

static void set_key(key_name_t name)
{
	if(key_number >= KEY_MAX)
		return;

	key_names[key_number] = name;
	key_number++;
}

static key_name_t get_key_pushed(void)
{
	// need to implement with GPIO Level

	return KEY_NONE;
}
 

이렇게 해서 Builder 패턴을 알아봤습니다. strategy 패턴과 유사하게 보이는데, Key 관리자가 하나의 Key 객체만 처리하도록 구현되어 있어서 그렇습니다. 여러 개의 Key 객체를 받도록 구현하면 builder 패턴을 좀 더 잘 이해 하실 수 있을 겁니다.

 

<End>

'▶ C Application > 디자인 패턴' 카테고리의 다른 글

C언어에서 static 반드시 써야 하는 이유  (0) 2024.02.24
Facade 패턴  (0) 2024.02.12
Adapter 패턴 - 3  (0) 2023.12.24
Adapter 패턴 - 2  (0) 2023.12.24
Adapter 패턴 - 1  (0) 2023.12.24