[강좌 : 객체지향 - 5] 캡슐화
2009.09.04 14:30
김성용(topmentor)
http://cafe.naver.com/gisdev/543
5. 캡슐화<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /><?xml:namespace prefix = o />
이번 시간은 캡슐화에 대해 알아보겠습니다. 개인적인 생각으로는 객체지향 4대 특징중에 어쩌면 가장 주목받지 못하는 특징이 아닌가 합니다. 그 이유는 캡슐화를 개념적인 측면으로만 접근하고 실제 코드로 별로 적용하지 않기 때문에 생기는 문제라고 봅니다. 캡슐화가 객체지향의 4대 특징으로 올라설 수 있었던 것은 그 만큼 개념적인 중요성이 크기 때문인데 많은 개발자들이 이것의 중요성에 대해 인식하지 못하고 있습니다. 그도 그럴 것이 많은 객체지향 프로그래밍 책에서 캡슐화를 자세히 다루지 않기 때문입니다. 그저 접근지정자 설명에 그치는 경우가 대부분이죠. 하지만 캡슐화의 특징은 접근지정자 이상의 의미를 가지고 있고 실제 코딩에서는 다양한 형태로 구현이 됩니다.
자! 그럼 캡슐화로 들어가 보겠습니다.
가. 캡슐화 개요
잠시 복습하는 차원에서… 객체지향 4대 특징의 개념 시간에 캡슐화를 어떻게 설명했었죠?
네~ 그렇습니다. '데이터 구조'와 '데이터를 다루는 방법'를 결합시켜 묶는 것을 말합니다. 질환을 치료하는 데 필요한 몇몇 약들이 적정한 비율로 섞여 하나의 캡슐에 들어있는 알약을 예로 들어 설명했습니다. 그러면서 특정 객체가 독립적으로 역할을 제대로 수행하기 위해 필요한 데이터와 기능을 하나로 묶어 관리하는 것을 캡슐화라고 한다고 정리했었습니다.
거기에 데이터는 은닉하고 그 데이터를 접근하는 기능(함수)를 밖으로 노출하는 것도 캡슐화라고 했습니다. 데이터를 기능으로 보호하여 데이터의 입출력이 기능을 통해 통제되도록 설계하는 것을 말하는 것이었죠.
지금까지의 내용을 정리해 보면 캡슐화는 두가지 측면이 있습니다.
1) class안에 관련된 변수와 메소드를 묶어 넣는 것.
2) 정보은닉
: 객체는 자신이 수행하는 모든 행동의 과정이나 속성을 외부로 보이지 않게 합니다.
즉, 외부에서는 사용하는 메소드의 로직이나 멤버변수들에 대해서는 알 필요가 없다
는 것입니다.
나. 캡슐화의 구현
캡슐화를 구현하기 위해 각 객체지향언어들은 접근지정자라는 것을 제공합니다. 클래스의 멤버에 접근할 수 있는 권한을 통제하여 적절히 보안정책을 취할 수 있게 하는 것입니다. 대표적인 접근지정자는 다음의 3가지 입니다.
. private : 클래스 내부 (멤버함수)에서만 접근가능
. protected : 클래스 내부 + 하위 클래스의 멤버함수에서 접근 가능
. public : 클래스 내부 + 하위 클래스의 멤버함수 + 외부 함수에서 접근 가능
class 클래스 이름
{
public :
데이터형 멤버변수1;
데이터형 멤버변수2;
리턴형 함수1 ( 매개변수 );
private:
데이터형 멤버변수3;
데이터형 멤버변수4;
리턴형 함수2 ( 매개변수 );
};
위 예에서 멤버변수1, 멤버변수2, 함수1은 pulic 접근지정이 됩니다. 그럼 대충 눈치로 보면 멤버변수3, 멤버변수4, 함수2는 private 지정이 되겠구나라는 짐작이 가시죠? 클래스 내부에 인스턴스가 관련 기능을 온전히 하기 위해 필요한 데이터와 함수를 한묶음으로 가지고 있다면 이것은 캡슐화가 잘 된 것입니다. 캡슐화가 잘 된 클래스는 외부에 의존하는 정도가 줄어들겠죠.
두번째 캡슐화 정의를 구현하는 방법은 다음과 같습니다. 모든 멤버변수는 private로 접근지정을 해서 외부의 직접접근을 문법적으로 차단 시키고 멤버 변수 데이터를 수정하거나 데이터 값을 알아내기 위한 별도의 멤버 함수를 제공하는 것입니다. 이렇게 멤버변수를 수정하거나 값을 열람하는 접근함수를 Getter(accessor)/Setter(mutator)라고 합니다.
아래 예를 잠깐 살펴 보겠습니다.
class Car
{
private :
int speed;
public:
void setSpeed(int n) { speed = n; }
int getSpeed() { return speed; }
};
int main()
{
Car mycar;
mycar.setSpeed(100);
cout << mycar.getSpeed() ;
}
예제를 보면 speed라는 멤버변수는 private로 묶어 두었기 때문에 main함수에서는 직접 접근할 수 없습니다. 대신 이 멤버에 접근하기 위한 메소드를 제공하고 있는 것을 알 수 있습니다. speed에 데이터를 입력하기 위해 setSpeed()를 제공하고 speed 값을 얻어내기 위해 getSpeed()를 제공하고 있습니다. 그래서 main함수에서도 이들 함수를 호출하여 간접적으로 speed 멤버를 쓰고 있습니다. 함수를 통한 데이터의 접근, 이것이 바로 캡슐화인 것입니다.
아! 저기서 누가 손들고 계시는군요.
‘speed를 public로 해 두면 굳이 메소드를 만들지 않고 편하게 접근해서 쓸 텐데 이런 번거로운 것을 해야 하나요?’
네~!! 맞습니다. 그렇게 쓰면 편하죠. 그런데 이런 측면을 생각해 볼까요? 멤버변수중에 외부에서 값은 열람은 하되 수정은 하지 못하게 하고 싶다면 어떻게 해야 할까요? 멤버변수를 public로 그냥 노출하면 외부에서 수정하는 것을 원천적으로 차단할 수 없겠죠? Getter와 Setter를 선택적으로 두면 그 멤버변수는 읽기전용 혹은 쓰기전용식으로 데이터에 대한 접근권한을 더 세분화 할 수 있습니다.
또한 위 예제의 로직을 잘 보세요? speed멤버변수는 음수값이 들어오면 안 됩니다. 마이너스 속도란게 있을 수 없잖습니까? 만약 speed가 노출되어 있다면
speed = -100;
식으로 음수가 입력되는 것을 막을 수 없습니다. 하지만 메소드를 통해 간접적으로 접근하게 하면 이런 입력값의 유효성을 체크할 장치를 둘 수 있습니다. setSpeed()에서 두면 되겠죠.
void setSpeed(int n) {
if(n>=0)
speed = n;
else
speed = 0;
}
위와 같이 해 두면 아무리 main함수에서 ‘mycar.setSpeed(-2000);’ 한다 한들 speed에는 음수값이 입력되지 않습니다. 자~ 그럼 정리해 봅시다. 멤버변수를 멤버함수로 캡슐화하는 이유는
1) 데이터에 대한 읽기 쓰기 권한 구분
2) 입력데이터의 유효성 검증 부분을 로직에 포함 시킬 수 있음.
입니다. 정리가 되셨죠? 실제 COM과 같은 컴포넌트 프로그래밍에서는 외부에서 접근할 필요가 있는 멤버변수에 대해 Getter와 Setter를 반드시 두도록 되어있습니다.
다. 클래스 설계상의 캡슐화
세번째 캡슐화에 대해 설명 드리겠습니다. 이것을 객체지향 프로그래밍 책에는 소개되어 있지 않지만 실제 프로그래밍에서는 많이 활용되고 있는 기법입니다. 이것은 설계적인 측면이 있어서 이해하는 데 좀 어려울 수도 있습니다. 따라서 지금 이해가 안 되더라도 양심의 가책(?) 느끼지 마시고 넘어가셔도 됩니다. 다형성을 이해한 다음에 보시면 됩니다.
세번째 캡슐화는 선언만 노출하고 구현은 숨기는 것입니다. 잘 이해가 안 되시죠? 이와 비슷한 기법을 쓴 것이 바로 라이브러리입니다. C나 C++라이브러리를 이용할 때 보면 코딩시에 추가(include)하는 것은 라이브러리의 헤더입니다. 헤더에 드러난 함수나 클래스 선언을 보고 자신이 작성한 코드에서 라이브러리 함수를 호출합니다. 마치 자신이 작성한 함수처럼 말이죠. 그럼 그 라이브러리 함수의 실제 구현부분은 어디에 있는 것일까요? Lib 파일에 네이티브 코드형태로 있다가 link시에 이들 코드가 삽입되는 것입니다. 이것도 캡슐화입니다. 세부적인 구현은 숨기고 외부에는 사용할 수 있는 최소의 정보만 노출하는 것이면 모두 캡슐화입니다.
객체지향 설계에 있어서 캡슐화는 클래스의 선언만 담는 순수추상클래스를 전면에 내세워 외부에서는 이것을 사용하게 하고 순수추상클래스를 상속받는 클래스를 만들어 여기에 실질적인 구현을 하는 것입니다. 구조로 보면 아래와 같습니다.
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" /><?xml:namespace prefix = v />
위 그림에서 ImageSource라는 추상클래스는 구현부가 없습니다. 그냥 멤버함수의 선언만 들어있습니다. 이것이 인터페이스 역할을 하는 것입니다. 그 멤버함수의 구현은 하위 클래스인 LocalImageSource나 RemoteImageSource에 있습니다.따라서 외부에서는 ImageSource라는 추상클래스를 활용하고 실제 기능의 수행은 하위클래스인 LocalImageSource나 RemoteImageSource에서 이루어집니다. 이렇게 되면 클래스를 사용하는 측에서는 세부구현을 신경쓸 필요없이 ImageSource가 노출하는 인터페이스만 보면 되는 겁니다.
이렇게 함으로 해서 클래스 사용하는 쪽이 클래스의 실제 구현부를 건드려 클래스 개발에 대한 일관성을 잃게 되는 위험을 피할 수 있습니다. 클래스 구현의 버전관리에도 강점이 있죠. 아무튼 이 기법은 클래스 설계상 구현할 수 있는 캡슐화입니다. 자바를 좀 하신 분은 JDBC가 이런 구조를 가지고 있다는 것을 아실 겁니다. 세번째 기법은 나중에 다형성이 끝난 후 디자인 패턴을 이야기 할 때 자세한 코드를 예로 들어 설명하겠습니다.
정리하죠. 캡슐화는 실질적인 것은 숨기고 접근할 방법만 노출하는 것을 의미한다. 요즘에는 관련된 것을 묶는 다는 의미는 많이 퇴색되었습니다.