캡슐화의 뜻을 이해하기 전에 C에서 많이 사용하던 구조체(struct)를 잠시 보자.
struct Struct1
{
int i;
double d;
char c;
int (*foo)(int a, int b);
void(int(*bar)[])(int);
};
C언어에서 자주 사용하던 구조체는 다음과 같이 사용했다. struct 내부의 멤버들의 타입들을 지정해 줌으로서 사용할 수 있었다. C++의 Class는 이 Struct에서 struct 지정자를 class로 바꿈으로서 시작된다.
class Struct1
{
int i;
double d;
char c;
int (*foo)(int a, intb);
void(int(*bar)[])(int);
};
하지만, class에는 지금 설명하는 캡슐화(Encapsulation)을 사용할 수 있다. 캡슐화란 일반적으로 연관있는 변수와 함수를 클래스로 묶는 것과 더불어, 객체의 데이터를 외부로부터 감출 수 있고, 기능 구현을 은닉할수 있다. (정보은닉)
캡슐화를 실현하기 위해 사용하는 것들이 바로 접근 지정자(Access Specifier)와 접근 함수(Access Function) 이다.
접근 지정자는 총 세가지로 public, private, protected가 존재한다. 클래스 내부에서 다음처럼 사용한다.
class Class1
{
private: // 기본적으로 클래스 내부 멤버는 private로 선언된다.
int p_i;
double p_d;
char p_c;
void foo() {};
public:
int pb_i;
double pb_d;
char pb_c;
void pub_foo() {};
protected:
int pt_i;
double pt_d;
char pt_c;
void pro_foo() {};
}
하나씩 그 범위를 알아보자
protected는 추후 상속에서 더욱 다루도록 하고, public과 private에 시점을 옮겨보자.
private 내부의 멤버에 접근하려면 어떡해야할까? 굳이 접근 해야하는 멤버를 private으로 선언하는것이 아니라 그냥 public으로 선언하면 되지 않을까? 하지만, 이는 큰 혼란을 가져올 수 있다.
캡슐화의 초점은 클래스 내부 메소드의 구현만 변경하면 되게끔 하는 것이다. 즉, 어느 한 기능이 수정 되었을때, 소스코드 전체를 수정하는 것이 아니라. 그 기능을 멤버로 지니고 있는 클래스 내부만 수정하면 된다는 소리이다.
이 예를 들기 앞서서 접근 함수(Access Function)을 예로 들어 설명한다. 접근 함수는 private 멤버에 접근하기 위해, public이나 protected 범주에 선언하는 함수이다. 대표적으로 Getter/Setter를 예로 들 수 있다.
class Class1
{
char target; // Basically Private
public:
void setTarget(const char& input)
{
target = input;
}
const char& getTarget()
{
return target;
}
}
...
int main()
{
Class1 testClass;
testClass.setTarget(3);
std::cout << testClass.getTarget() << endl;
return 0;
}
다음과 같이 사용한다. setTarget 메서드를 통해 private 멤버인 target 변수를 수정하고, getTarget을 이용해 받는다. 이러한, private 멤버와 외부를 연결 시켜주는 메서드, 함수를 접근 함수라고 한다.
그렇다면, 위에서 언급한 캡슐화의 초점을 위해 접근 함수가 기여하는 일이 무엇일까? 다음 예제를 확인해보자.
<클래스를 사용한 캡슐화>
class Person
{
string first_name = "Seyoung";
string last_name = "Kim";
int age = 28;
int height = 191;
int weight = 80;
public:
void printPersonInfo()
{
cout << first_name << " " << last_name << " " << age ... 생략
}
}
<구조체를 이용>
struct sPerson
{
string first_name;
string last_name;
int age;
int height;
int weight;
};
int main()
{
// 외부 사용 예시
struct sPerson person1;
Person person2;
<구조체를 사용할 시>
person1.last_name = ...
person1.first_name = ...
...
for(int i = 0; i < 5; ++i)
{
cout << *(&person+i) << " ";
}
cout << endl;
<클래스 사용시>
// Getter Setter 생략(클래스에서 임시로 미리 기본값 지정)
person2.printPersonInfo();
return 0;
}
위의 예시를 보자, 클래스를 사용하면, 구조체를 이용해 만든 것과 같은 기능을 하더라도, 외부에서는 메서드 하나로 표현 할 수 있게 된다. 여기서, 만약 printPersonInfo() 메서드에, 각각의 출력값이 무엇을 의미하는지 메서드를 수정해야한다고 생각해보자, 또, Person정보 안에 가족의 수라는 새로운 변수가 생긴다면? 그리고 이러한 메서드가 전반적으로 여러번 사용된다면? 구조체를 이용한 경우 어떻게 하겠는가?
구조체의 멤버를 추가하고, 출력하는 곳 마다. 일일이 수정을 거쳐야 할 것이다.
클래스를 사용한 경우는 printPersonInfo() 라는 클래스 내부의 메서드 한줄과 클래스 멤버만 수정해주면 된다. 이렇게 private과 접근 함수를 통해 캡슐화를 실현시킬 수 있다. 이는 정보은닉과, 객체지향의 재사용성을 실현시킴을 알 수 있다.