[C++] 4.정보은닉과 캡슐화


정보은닉의 이해



우리는 객체의 생성을 목적으로 클래스를 디자인 합니다.

그리고 좋은 클래스가 되기 위한 조건은 ‘정보은닉’과 ‘캡슐화’를 사용하는 것입니다.

먼저, 정보은닉이 무엇인지에 대해서 알아보겠습니다.

예를 들어, 0부터 100사이의 점의 좌표를 나타내는 클래스를 정의하고 싶다고 가정하겠습니다.

//클래스 Point의 멤버 변수를 public으로 설정
class Point
{
public:
	int x;
	int y;
};



위와 같이 정의를 하면 문제점이 발생합니다~


Point pt={-1, 30};



위와 같이 x좌표가 -1인데도 불구하고 컴파일에러가 발생하지 않습니다~

정말 심각한 문제이죠~ 잘못되었지만 그 사실을 알기 힘듭니다.

어쩌다 실수를 했을 수는 있지만 실수의 대한 대책이 하나도 준비되어 있지가 않습니다.

때문에 제한된 방법으로의 접근만 허용을 해서 잘못된 값이 저장되지 않도록 도와야하고,

또, 실수를 했을 때, 실수가 쉽게 발견되도록 해야합니다~

이 때 사용되는 개념이 ‘정보은닉’ 입니다~

정보은닉이란 멤버변수를 private로 선언하고, 해당 변수에 접근하는 함수를 별도로 정의해서,

안전한 형태로 멤버변수의 접근을 유도하는 것입니다.

그럼, 정보은닉을 사용하여 다시한번 클래스 Point를 정의해보겠습니다.


Point.h



#ifndef __POINT_H__
#define __POINT_H__

class Point
{
private: //멤버 변수 private로 선언
	int x;
	int y;

public:
	bool Init(int xpos,int ypos); //초기화 함수
	int GetX() const; 
	int GetY() const;
	bool SetX(int xpos);
	bool SetY(int ypos);
};
#endif




Point.cpp


#include <iosteam>
#include "Point.h"
using namespace std;

bool Point::Init(int xpos, int ypos)
{
	//가능 범위를 정해줍니다
	if(xpos<0||xpos>100||ypos<0||ypos>100)
	{
		cout<<"벗어난 범위의 값 전달"<<endl;
		return false;
	}
	
	x=xpos;
	y=ypos;
	return true;
}


int Point::GetX() const //const 함수
{
	return x;
}


int Point::GetY() const
{
	return y;
}


bool Point::SetX(int xpos)
{
	if(0>xpos||xpos>100)
  {
		cout<<"벗어난 범위의 전달"<<endl;
		return false;
	}
	x=xpos;
	return true;
}


bool Point::SetY(int ypos)
{
	if(0>ypos||ypos>100)
	{
		cout<<"벗어난 범위의 전달"<<endl;
		return false;
	}
	y=ypos;
	return true;
}



위와 같이 정의하면 잘못된 값이 전달되었을 때 알기 쉽겠죠??


그리고 

int GetX() const;
bool SetX(int xpos);
int GetY() const;
bool SetY(int ypos);

위의 함수를 가리켜 엑세스 함수라 하는데, 이들은 멤버변수를 private로 선언하면서,

클래스 외부에서의 멤버변수 접근을 목적으로 정의된 함수입니다.



또 GetX() 함수 옆에 const 가 붙어있는데 , 이를 const함수라고 합니다.


const 함수

int GetX() const;

이 const는 다음 내용을 선언하는 것입니다.

"이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다!"

따라서 실수로 값을 변경하는 것을 막아줍니다~!

그리고 const 함수 안에서 const가 아닌 함수의 호출 또한 제한됩니다!

const가 아닌 함수는 값을 변경할 능력을 가졌기 때문입니다!



캡슐화



캡슐화는 둘 이상의 기능이 모여서 하나의 목적을 달성하는 것입니다.

쉽게 말해서 하나의 목적을 위해 여러 클래스가 묶인 형태인데요~

예를 들어 설명해보겠습니다.

코감기는 재채기와 콧물, 코막힘을 동반합니다.

따라서 코감기 알약 속에는 재채기 완화 성분, 콧물 완화 성분, 코막힘 완화 성분이 있어야합니다.

각각의 완화 성분이 클래스로 정의되어 있다고 합시다


class SinvelCap //콧물 완화 성분
{
public:
	void Take() const
	{
		cout<<"콧물 완화 성공"<<endl;
	}
};

class SneezeCap //재채기 완화 성분
{
public:
	void Take() const
	{
		cout<<"재채기 완화 성공"<<endl;
	}
};

class SnuffleCap //코막힘 완화 성분
{
public:
	void Take() const
	{
		cout<<"코막힘 완화 성공"<<endl;
	}
};



반드시 콧물 성분, 재채기 성분, 코막힘 성분 순으로 복용을 해야한다고 할 때,

각각의 완화 성분을 따로 정의만하면, 너무 복잡하고,

또 다음과 같이 순서가 바뀌는 실수가 발생할 수 있습니다.


int main(void)
{
	SinvelCap scap; //콧물 완화
	SneezeCap zcap; //재채기 완화
	SnuffleCap ncap; //코막힘 완화
	
	//약 복용 (코드가 너무 복잡함)
	
	//콧물 - 재채기 - 코막힘 순으로 복용해야하지만 순서 잘못
	
	zcap.Take(); //재채기 완화 복용
	scap.Take(); //콧물 완화 복용
	ncap.Take(); //코막힘 완화 복용

		
//실수를 했지만, 컴파일 에러가 발생하지 않아 실수가 발생했는지 알기가 힘듭니다.



이를 해결하는 것이 캡슐화 인데요~

완화성분들을 한 알약에 넣고 정보은닉을 사용해서,

복용함수를 정의해준다면 됩니다!


class SinvelCap //콧물 완화 성분
{
public:
	void Take() const
	{
		cout<<"콧물 완화 성공"<<endl;
	}
};

class SneezeCap //재채기 완화 성분
{
public:
	void Take() const
	{
		cout<<"재채기 완화 성공"<<endl;
	}
};

class SnuffleCap //코막힘 완화 성분
{
public:
	void Take() const
	{
		cout<<"코막힘 완화 성공"<<endl;
	}
};


//캡슐화

class Cap
{
private: // 각각의 완화성분을 private로 선언
	SinvelCap sin;
	SneezeCap sne;
	SnuffleCap snu;

public: //콧물 - 재채기 - 코막힘 순으로 복용하는 함수
	void Take() const
	{
		sin.Take();
		sne.Take();
		snu.Take();
	}
};



캡슐화가 쉽게 느껴질 수도 있지만, 캡슐화는 어려운 개념입니다.

왜냐하면 캡슐화의 범위를 결정하는 일이 쉽지 않기 때문입니다~~

정답이란 것이 딱히 없는 개념이죠~

따라서 오랜 시간을 두고 다양한 사례를 접하면서 길러져야 합니다!!