while (1): study();

[C++] 정보은닉과 캡슐화 본문

학습/C, C++

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

전국민실업화 2021. 12. 19. 21:58
728x90

 절차지향적 언어였던 C에서 객체지향적 언어인 C++로 넘어오면서 생긴 주요한 변화 중 하나는 class 키워드의 존재이다. class는 클래스를 생성하는 키워드이다. 그렇다면 클래스는 어떤 장점이 있기에 언어를 따로 만들면서까지 객체지향 프로그래밍을 실현하려고 한 것일까? 객체지향 프로그래밍의 장점은 더더욱 많겠지만, 여기선 클래스의 장점에 국한해서 이야기한다. 클래스의 장점에는 정보 은닉과 캡슐화가 있다.

 우선 정보 은닉부터 보자. 정보 은닉이란 멤버 변수에 대한 직접적인 외부 접근을 차단하는 것이다. 이전에도 복합 데이터 구조를 "구조체"로 지원했던 C였지만, 구조체와 클래스는 결정적인 차이가 있다.

 구조체는 멤버를 public으로 초기화하고, 클래스는 멤버를 private으로 초기화한다.

이 간단한 차이는 생각보다 많은 것을 이야기한다. 만약 클래스의 멤버를 아무 제한없이 접근 및 변경할 수 있다면 개발자의 실수로 인해 예기치 않은 오류가 발생할 수 있을 뿐더러, 익명의 데이터 접근 시도에도 무방비해진다. 이때 멤버를 private으로 초기화하면 멤버 함수를 이용하지 않는 한 접근할 수 없는 일종의 방어책이 생기게 된다.

#pragma once
#include <iostream>

using namespace std;

class FruitSeller
{
private:
	int APPLE_PRICE;
	int numOfApples;
	int myMoney;
public:
	bool InitMembers(int price, int num, int money);
	int SaleApples(int money);
	void ShowSalesResult() const;
};

class FruitBuyer
{
	int myMoney;
	int numOfApples;
public:
	bool InitMembers(int money);
	bool BuyApples(FruitSeller& seller, int money);
	void ShowBuyResult() const;
};

위와 같은 클래스의 안전성을 보장하기 위해서 다음과 같이 클래스를 정의한다.

#include <iostream>
#include "person.h"
#define MSG	"잘못된 범위의 값"

using namespace std;

bool FruitSeller::InitMembers(int price, int num, int money)
{
	if (price < 0 || num < 0 || money < 0)
	{	
		cout << MSG << endl;
		return false;
	}
	APPLE_PRICE = price;
	numOfApples = num;
	myMoney = money;
	return true;
}
int FruitSeller::SaleApples(int money)
{
	int num = money / APPLE_PRICE;
	numOfApples -= num;
	myMoney += money;
	return num;
}
void FruitSeller::ShowSalesResult() const
{
	cout << "남은 사과 : " << numOfApples << endl;
	cout << "판매 수익 : " << myMoney << endl << endl;
}

bool FruitBuyer::InitMembers(int money)
{
	if (money < 0)
	{
		cout << MSG << endl;
		return false;
	}
	myMoney = money;
	numOfApples = 0;
	return true;
}
bool FruitBuyer::BuyApples(FruitSeller& seller, int money)
{
	if (money < 0)
	{
		cout << MSG << endl;
		return false;
	}
	numOfApples += seller.SaleApples(money);
	myMoney -= money;
	return true;
}
void FruitBuyer::ShowBuyResult() const
{
	cout << "현재 잔액 : " << myMoney << endl;
	cout << "사과 개수 : " << numOfApples << endl << endl;
};

 위의 코드에서 멤버변수의 직접 접근을 차단하고, 출력함수를 따로 구현하여 안전성을 향상시킨 것을 확인할 수 있다. ShowSellResult()와 ShowBuyResult() 뒤에는 const 키워드가 붙는데, 이는 멤버변수를 수정하지 못하게 방지한다. 주의할 것은, const 키워드가 붙은 함수 내에서는 또 다른 const 함수만을 사용할 수 있으며, const 참조자로 객체가 주어졌을 경우에도 마찬가지로 const 함수만을 사용할 수 있다. 이는 정보 은닉을 지원하는 c++의 특징이다.

 추가적으로 멤버 함수를 통해 변수에 접근하게 되면, 예외처리도 용이하다는 장점이 있다. 위의 코드에서는 음수인 값을 받지 못하도록 예외처리를 한 것이 그 예시이다.

 두 번째로 캡슐화는 관련된 변수나 함수를 동시에 관리하는 것을 의미한다. 예를 들어 감기를 낫게 하기 위해 콧물이 멎는 약, 재채기가 멎는 약, 코가 뚫리는 약을 따로 먹으려면 여간 귀찮은 것이 아니며, 약들간의 상호관계가 있어 복용 순서가 중요하다면 더 심각한 이야기가 된다. 따라서 객체를 통한 캡슐화는 코드를 단순화시켜주며, 멤버 간 관계도 잘 표현할 수 있는 유용한 수단이다.

#include <iostream>
using namespace std;


class SinivelCap
{
public:
	void Take() const {
		cout << "콧물이 싹~ 납니다." << endl;
	}
};

class SneezeCap
{
public:
	void Take() const {
		cout << "재채기가 멎습니다." << endl;
	}
};

class SnuffleCap
{
public:
	void Take() const {
		cout << "코가 뻥 뚫립니다." << endl;
	}
};

class CONTAC600
{
private:
	SinivelCap sin;
	SneezeCap sne;
	SnuffleCap snu;
public:
	void Take() const
	{
		sin.Take();
		sne.Take();
		snu.Take();
	}
};

class ColdPatient
{
public:
	void TakeCONTAC600(const CONTAC600& cap) const { cap.Take(); }
};

int main(void)
{
	CONTAC600 cap;

	ColdPatient sufferer;
	sufferer.TakeCONTAC600(cap);
	return 0;
}

 위의 예에서 각각 콧물이 멎는 약, 재채기가 멎는 약, 코가 뚫리는 약이 따로 있지만 이를 따로 복용할 것이 아니라 하나의 감기약으로 복용하는 것이 훨씬 편리하다. 한편 캡슐화는 결국 정보 은닉을 동반하는 개념으로 볼 수 있다.

728x90
Comments