본문 바로가기

프로그래밍/C++

물체로 알아보는 객체 지향 1

서 론

이 글은 객체 지향의 개념을 잘 아는 사람에게는 객체의 개념을 다시 생각 해볼 기회를 가져보기 위해, 객체 지향의 개념을 잘 모르는 사람에게는 객체의 개념을 파악하기 위해 쓰여졌다. C의 기본적인 문법을 알고 있지 않다 하더라도 객체라는 것을 이해하는 데 무리가 없도록 설명할 것이다. 객체 지향은 90년도에 나온 개념으로 그렇게 새로운 개념은 아니다. 하지만 일반인이 쉽게 접할 수 없는 개념임에는 틀림없다. 본 저자가 C++ 및 Java를 약 3년간 사용해보면서 생각한 바와 함께 적어보고자 한다.


여기에서는 OOP하면 항상 따라오는 개념(정보 은폐, 캡슐화, 다형성)을 설명하기 보다는 더 직관적인 내용으로 설명하고자 한다. OOP라는 개념을 만든 사람의 생각과는 꽤 다를지도 모르겠다. 어디까지나 본 저자의 개인적인 생각이 다분히 포함되어 있는 것을 염두에 두고 읽어주기 바란다.


객체는 무엇인가

객체. 정말 한글로 번역해 놓은 것이지만 그것 자체로는 정말 이해되지 않는 것 중에 하나다. 주체나 객체. 이런 것을 영어 교과서에서 또는 다른 언어 교과서에서 들어본 적이 있지 않은지? 저 정말 무엇을 말하려고 저런 단어가 탄생했는지 필자는 꽤나 저 단어들을 꽤나 저주했다. 번역체의 어투가 있듯이 저 단어는 번역체의 단어라고 생각한다.


객체를 영어로 하면 Object. 안타깝게도 번역하라고 하면 결국 나 역시도 객체가 될 것 같다. 하지만 객체가 대체 무엇이길래? 나는 이 객체를 차라리 어떤 물체라고 번역하고 싶다. 객과 물의 차이밖에 없지만, 내가 의도하고자 하는 것은 객이라고 하는 정말 알 수 없고 모호한 개념 대신에 어떠한 실재적인 무언가를 나타내는 단어인 물을 차라리 집어넣자는 것이다.


자 그럼 Object-Oriented Programming은 물체 지향 프로그래밍이라 볼 수 있겠다. 물체를 지향한다. 그렇다면 프로그래밍에서 말하는 물체는 무엇이라고 생각해야 할까? 물체는 물체지만 이 물체는 어떠한 조작을 가하면 스스로 변한다. 따라서 단순히 물체라고 보기보다는 어떤 기계라고 보는 것이 좀 더 정확할 것 같다. 이 기계는 일정한 조작법이 있어서 그 조작법에 맞게 조종하면 기계는 스스로 변한다. 이 기계는 부서질 때까지 계속 동작하며 가끔 다른 결과물을 내뱉기도 한다. (예를 들면 증기기관 같은 기계의 온도 계기판 같은 것을 생각하면 되겠다.)


기계가 자신의 모습을 스스로 바꾼다는 개념은 앞으로의 설명의 가장 중요한 개념이 될 것이다. 독자들은 이 말을 꼭 염두에 두길 바란다. 객체 지향에 대한 글을 마칠 때 처음이나 끝이나 가장 강조하는 바는 기계스스로 변한다는 것이다.


기계는 스스로 변한다

기계는 일종의 고유한 세계이다. 기계 내부는 여러 설정들과 상황으로 이루어져 있다. 여러분이 기계를 통해 특정 조작을 할 때마다 기계는 자기 자신의 모습을 스스로 바꾸어간다.


여기서 슬슬 기계의 정체를 밝혀야 될 듯 하다. 기계, 기계 했던 그 정체는 바로 class이다. 변수보다는 좀 더 많은 정보를 저장할 수 있고 변수처럼 선언해서 사용하는 문법이다. 자, 이 기계는 변수처럼 조작을 가할 수 있고 그 내용이 변한다. 이 말이 포함하는 바는 기계가 일을 하기 위해 만들어진 뒤 새로 생성되는 것이 아니라는 것이다. 자기 자신을 복제한 녀석을 나오게하는 것을 제외하면 자기 자신 외에 다른 기계가 나오는 것을 가능한 억제하도록 해야된다는 것이다. 기계는 자기 자신이 변할 뿐 새로운 어떤 기계를 내놓지는 않는다.



class A{
A a();
A b();
}

// 이와 같이 자신과 동일한 기계를 만들어 내놓는 것은
// 가능하면 지양해야한다는 뜻이다.
A A::a(){
// 이 구문은 새로운 기계를 만들겠다는 구문이다.
A newA = new A();
.. Do something ..
return newA;
}

// 아래 예제와 같이 가능하면 자기 자신을 변경한 뒤
// 그 자신을 내놓는다.
A A::b(){
.. Do Something ..
// 이 구문은 이 기계를 반환값으로 주겠다는 이야기이다.
return this;
}

여기서 왜 객체 지향을 말하면서 제한 사항만 이야기하는 것인지 궁금해하는 사람도 있을것이라 생각한다. 여기서 말하고자 하는 것은 언어의 가능성이 아니라 어떻게 하면 언어를 객체 지향적으로 짤 수 있는가를 논하는 것이다. 좀 더 기계처럼 보이기 위하여, 가독성을 높이고 그 효율성도 높이고자 하는 것이다. 프로그래밍을 하는 사람들은 기계를 계속해서 만드는 것보다는 가능하면 하나 또는 소수의 기계를 조작하여 그 기계를 이용하는 것을 권장하는 바이다.


여기 하나의 예제를 더 들어보자.



int a, b, c;
a= 1
b= 2
c= a+ b

이 구문에서는 a라는 기계가 1을 지니고 b라는 기계가 2를 지닌다. 그리고 c라는 기계는 a라는 기계가 지닌 값과 b라는 기계가 지닌 값을 더한 값을 지닌다. 덧셈이란 연산을 하기 위해서 3 개의 기계가 쓰였고 이는 객체 지향에서 권장하는 바가 아니다.



A a;
A.set(1);
A.add(2);

바로 이런 것이 권장되는 바이다. 1+ 2를 구하기 위해 A의 형식의 기계 a를 장만하였고 이 기계를 먼저 1로 설정한 뒤 2를 더해주었다. 새로운 기계는 나오지도 않았고 기계의 상태는 (초기값), 1, 3으로 착착 변하였다. 이러한 코드가 객체 지향에서 권장되는 코드이다.


기계의 조작을 가한 결과는 역시 그 기계이다

이 부분은 위의 항목에서 이미 언급한 내용이다. 이 내용은 기계를 사용한 결과는 가능하면 그 자신의 기계를 반납하라는 것이다.



class A{
A b();
}

// 아래 예제와 같이 가능하면 자기 자신을 변경한 뒤
// 그 자신을 내놓는다.
A A::b(){
.. Do Something ..
// 이 구문은 이 기계를 반환값으로 주겠다는 이야기이다.
return this;
}

C에서는 어떠한 함수가 제대로 임무를 완수하였는지를 확인하기 위해 정수형 반환값을 받았다.



int foo(){
.. Do Something ..
if success return 0;
else return 1;
}

int main(){
if( foo() == 1 ) print error;
}

어떠한 숫자를 성공으로, 어떠한 숫자를 실패로 할 것인가는 각 프로그래머의 취향이다. 따라서 특별한 주석 또는 설명이 없다면 반환값이 무엇인지를 판별하기 까다로웠다. 그래서 객체 지향 프로그래밍에서 나오게 된 것이 바로 예외(Exception)이다. 보통 실패하는 경우에 어떠한 이유로 실패했는지를 알려주기 위해 여러 분기로 나뉜다. 반환값 1은 정수를 0으로 나누어서 실패했다든지, 반환값 2는 절대 가지 말아야할 상태로 빠졌다든지 말이다. 하지만 예외를 이용하면 성공은 그냥 성공으로 처리하고 실패일 경우에만 예외를 던진다. 그러면 이용자는 던진 예외를 잡아서 알맞은 처리를 하면 되는 것이다.



// 위의 코드를 다음과 같이 사용할 수 있다.
A A::foo2(){
.. Do Something ..
if success return this;
// 예외를 던지고
else throw exception;
}

int main(){
A a;
try{
a.foo2();
// 그 예외를 낚아챈다.
}catch exception{
.. Handle failure ..
}
}

자 그럼 꼭 결과가 올바르게 나왔는지를 확인하기 위해 반환값을 이용할 필요가 없어졌다. 그렇다면 이 남는 반환값을 무엇으로 사용하는 게 좋을까? 필자는 기계 자신을 반납할 것을 권장하고 싶다. 기계 자신을 반납할 경우 원하는 내용을 여러 줄이 아닌 한 줄에 입력할 수 있어 가독성이 높아진다.


일례로 위의 덧셈 예제 코드를 변경하면 다음과 같이 된다.



// 이 문법이
A a;
A.set(1);
A.add(2);

// 이렇게 한 줄로 변한다.
A a;
A.set(1).add(2);

여기서는 단순히 두 줄을 한 줄로 고쳤을 뿐이지만 여러 작업을 해야할 때에는 더 읽기 편하게 나올 것이다. (디버깅 시에는 한 줄로 몰아하는 것을 좋아하지는 않을 것이지만, 그 함수가 무결하다면 강관없을 것이다.) 이러한 자기 자신을 반납하는 가장 좋은 예제는 흐름(stream)이다. 각각 Java와 C++의 문법으로 표현해보겠다.



// Java
String foo(String k){
StringBuffer sb;
sb.append("Your name is ").append(k).append(". ").append(k).append(", Open your eyes.");
return sb.toString();
}

// C++
string foo(string k){
stringstream ss;
ss << "Your name is " << k << ". " << k << ", Open your eyes.");
return ss.c_str();
}

이와 같이 함수를 차곡차곡 포개어 놓는 것을 카스케이딩(Cascading)이라 한다. 저런 함수들을 한 줄에 하나씩 줄줄 써내려간다면 꽤 귀찮아하는 데다가 가독성도 떨어질 것이다. 기왕 void로 반환할 것이라면 기계 자체를 반납해서 언제든 저런 카스케이딩을 이용할 수 있게 하는 것은 어떨지?

'프로그래밍 > C++' 카테고리의 다른 글

단일체  (8) 2006.03.08
Java의 Package  (0) 2006.02.22
[C++]예외 처리  (0) 2006.02.03
[C++]클래스와 구조체  (2) 2006.02.03
여기서 교과서적인 이야기 하나.  (3) 2005.08.10