[C언어로 객체지향의 특성을 구현해보자] 1 - 캡슐화

Mobius·2023년 4월 16일

캡슐화는 객체에 속성과 기능을 넣어주는 작업이라고 설명했다. 우선 속성을 넣어주는 작업에 대해 알아보자.

1. 속성 캡슐화

int anna_height = 168;
int anna_weight = 48;

int mobius_height = 169;
int mobius_weight = 65;

위의 예시는 변수의 이름을 통해 annamobius를 캡슐화한 것이다. 당연한 말이겠지만, 이 캡슐들은 이 코드를 보는 사람만이 알 수 있는 사실이지 컴퓨터는 캡슐이라는 존재 자체를 모를 것이다. 그래서 이를 암묵적 캡슐화라 부를 것이다.

하지만 우리에게 필요한 것은 컴퓨터도 알 수 있게 명시적 캡슐화를 제공하는 것이다. 다행히도 C 언어는 구조체를 통해 이를 제공한다.

typedef struct {
	int height, weight;
} Person_t;

Person_t anna, mobius;

anna.height = 168, anna.weight = 48;
mobius.height = 169, mobius.weight = 65;

우리는 다음과 같이 height와 weight라는 속성을 Person_t라는 객체에 담았다. 이제 기능을 담아보자.

2. 기능 캡슐화

C 언어에서는 명시적으로 기능 캡슐화를 제공하지 않는다. 그래서 우리는 마음에 들지 않지만, 암묵적으로 기능 캡슐화를 해야 한다.

이를 하기 위한 과정은 다음과 같다.

  1. 기능을 캡슐화하기 위해 함수를 사용한다. 하지만 C 언어의 구조체는 함수를 가질 수 없기 때문에 이는 구조체 바깥에 존재해야 한다.
  2. 1 에서 정의한 함수는 인자 중 하나로 반드시 구조체 포인터를 받아야 한다. 이를 통해 객체를 읽거나 수정할 수 있다.
  3. (Optional) 속성 구조체의 선언이 있는 헤더 파일에서 함수를 선언한다.
// person.h

#ifndef Person_H
#define Person_H

typedef struct {
	int height, weight;
    int hungry;
    int intelligence;
} Person_t;

void Person_consructor(Person_t*, int, int, int);
void Person_handle_weight(Person_t*, int);
void Person_study(Person_t*, int);
void Person_drink(Person_t*, char*, int);
void Person_destructor(Person_t*);

#endif
// person.c

#include <string.h>
#include "person.h"

void Person_constructor(Person_t *person, int h, int w, int i) {
	person->height = h;
    person->weight = w;
    person->intelligence = i;
    person->hungry = 50;
}

void Person_handle_weight(Person_t *person, int k_calories) {
	person->weight += k_calories;
    
    if (k_calories > 0) {
    	person->hungry = 100;
    }
    
    else if (k_calories < 0) {
    	person->hungry -= k_calories / 100;
        
        if (person->hungry < 0) {
        	person->hungry = 0;
        }
    }
}

void Person_study(Person_t *person, int hour) {
	person->intelligence += hour * 5;
}

void Person_drink(Person_t *person, char *type, int amount) {
	if (strcmp(type, "alcohol") == 0) {
    	person->intelligence -= amount;
    }
    
    person->hungry += amount;
    if (person->hungry > 100) {
    	person->hungry = 100;
    }
}

void Person_destructor(Person_t *person) {
	// do nothing
}
// main.c

#include <string.h>
#include "person.h"

int main(int argc, char *argv[]){
	Person_t anna;
    
    Person_constructor(&anna, 168, 48, 200);
    
    // 하루 일과
    Person_handle_weight(&anna, 500); // 아침 식사
    Person_study(&anna, 2); // 공부
    Person_handle_weight(&anna, 800); // 점심 식사 (좀 거창하게)
    Person_study(&anna, 2); // 스터디
    Person_handle_weight(&anna, 500); // 저녁 식사
    Person_handle_weight(&anna, -400); // 운동
    Person_drink(&anna, "alcohol", 360); // 친구들과 술
    
    Person_destructor(&anna);
    
    return 0;
}

3. 정보 은닉

각각의 객체는 보여주면 안되는 정보들이 존재한다. 예를 들어, 사람의 몸무게는 비밀이다. 그래서 우리는 weight를 숨기도록 하자.

// person_private_weight.h

#ifndef Person_Private_Weight_H
#define Person_Private_Weight_H

struct person_t;

/**
	위의 메소드와 동일하게 선언
*/

int Person_get_height(struct person_t*);
int Person_get_intelligence(struct person_t*);
int Person_get_hungry(struct person_t*);

#endif

다른 소스파일이 위와 같은 헤더를 포함하는 경우에는 person_t의 내부에 접근하지 못한다. 그 이유는 현재 person_t는 단지 선언일 뿐이며, 내용의 정의되지 않았기 때문이다.

// person_private_weight.c

#include <string.h>

typedef struct {
	int height, weight;
    int intelligence;
    int hungry;
} person_t;

/**
	위의 메소드들과 동일하게 작성
*/

int Person_get_height(person_t *person) {
	return person->height;
}

int Person_get_intelligence(person_t *person) {
	return person->intelligence;
}

int Person_get_hungry(person_t *person) {
	return person->hungry;
}

위의 코드는 심지어 헤더파일까지 포함하지 않음으로써 외부로 절대 공개가 되지 않는다. 이것이 가능한 이유는 소스 파일들이 따로 컴파일 된후 마지막에 함께 링크되기 때문이다.

#include <string.h>
#include "person.h"

int main(int argc, char *argv[]){
	Person_t anna;
    
    Person_constructor(&anna, 168, 48, 200);
    
    // 하루 일과
    Person_handle_weight(&anna, 500); // 아침 식사
    Person_study(&anna, 2); // 공부
    Person_handle_weight(&anna, 800); // 점심 식사 (좀 거창하게)
    Person_study(&anna, 2); // 스터디
    Person_handle_weight(&anna, 500); // 저녁 식사
    Person_handle_weight(&anna, -400); // 운동
    Person_drink(&anna, "alcohol", 360); // 친구들과 술
    
    printf("anna's height: %d\n", Person_get_height(&anna));
    printf("anna's intelligence: %d\n", Person_get_intelligence(&anna));
    printf("anna's hungry: %d\n", Person_get_intelligence(&anna));
    
    Person_destructor(&anna);
    
    return 0;
}

위와 같이 우리는 getter 함수를 통해서만 anna의 정보에 접근이 가능한데, weight에 대한 getter 함수를 만들지 않아 weight에 접근할 수 없는 것을 볼 수 있다.

4. 빌드

gcc -c person_private_weight.c -o person_private_weight.o
gcc -c main.c -o main.o

만든 2개의 소스 파일을 목적 파일로 컴파일했다. 그 다음에 할 일은 이 목적 파일들을 링크하는 것이다.

gcc main.o person_private_weight.o -o person.out

이렇게 만들어진 person.out파일을 실행함으로써 원하는 결과를 얻을 수 있다.

5. 결론

이렇게 C 언어를 통해 캡슐화를 진행해보았다. 기능 구현의 경우에는 어쩔 수 없이 암묵적으로 구현하긴 했지만 그래도 정보를 은닉하는 것까지 구현함으로써 캡슐화의 목적은 달성했다고 본다.

다음 시간에는 상속을 구현해보도록 하자.


여담

새로운 구현에 대해서 링크 단계를 반복하고 싶지 않을 수도 있다. 그 때는 비공개 목적 파일을 포함하는 공유 라이브러리를 사용할 수도 있고, .so 파일을 사용할 수도 있다. 이 경우에는 런타임 중 동적으로 로드할 수 있으며 실행 파일을 링크하지 않아도 된다.

profile
Interested in many things.

1개의 댓글

comment-user-thumbnail
2023년 4월 18일

브이로그인데 왜 비디오가 없나요? 불편하네요.

답글 달기