현재 OOP를 지원하는 언어는 여러 가지가 있으나, 가장 대표적인 것으로는 C++와 JAVA, 그리고 4GL로 불리는 Perl, Tcl/Tk가 있다. 그 중에서도 Windows를 운영체제로 사용하는 로컬 머신의 애플리케이션의 개발에는 C++가 독보적으로 많이 쓰이고 있다.
이 C++는 그 언어를 설계할 때부터 OOP를 염두에 두고 만들었으며, 과거 OOP 언어들의 장점들은 모으고 단점들은 제거하거나 수정, 보완하여 흡수시킨 것이라고 할 수 있다.
OOP의 개념은 비교적 오래 전부터 제창되었으며, 당시 Pascal과 C로
대표되는 구조와 언어의 시대에서부터 그 명맥을 유지해 왔다. 최초의
OOP 언어는 1967년의 Simula67에서부터
시작된다. 그 다음 1980년 Smalltalk-80이 나왔는데, Simula67은 당시 Fortran에 밀려서 프로그래머들에게는 눈에 띄지도 않았고 단지 대학이나 연구기관에서 OOP를 연구할 목적으로 주로 사용했다. 하지만 Smalltalk의 경우에는 생각보다 많은 사람들이 사용하였고, 지금도
기억하고 있는 사람들이 많다. 현재 주목 받는 언어인 자바나 C++보다는 Smalltalk에 훨씬 가까운 언어이다. 또한 유명한 여성 프로그래머의
이름을 딴 Ada(에이다)라는 언어도 OOP 언어이다.
이후에는 OOP 전용의 언어보다는 기존 언어에 OOP 개념을 집어넣은 언어들이 많이 나왔다. 인공지능용 언어인 LISP도 지금은 OOP 개념을 집어넣은 언어들이 많이 나왔다. 인공지능용 언어인 LISP도 지금은 OOP 개념을 확장하여 ObjectLISP가 되었으며, 여러분이 잘 아는 Delphi에서 사용하는 언어도 그냥 파스칼이 아니라 ObjectPASCAL이다. 또 포트란도 ObjectFORTRAN이 되었다. 하지만 필자는 뭐니뭐니해도 현재는 C++를 잘 하는 것이 가장 중요한 시점이라고 생각한다.
객체(Object)란 무엇인가?
OOP의 핵심적인 용어는 '객체'이다. OOP는 쉽게 말하면 모든 것을 객체로 본다는 관점이다. 즉, 윈도우도 객체이고, 버튼도 객체이다. 글자도 객체이고, 사운도도 객체인 것이다. 객체는 우리가 무엇을 생각할 때 '개념적인 최소단위'라고 정의한다. 그러므로 일반적으로 우리가 단어를 붙일 수 있는 것은 거의 대부분 객체라고 할 수 있다. 왜냐하면 언어에서 개념적인 최소단위는, 즉 독립적인 의미를 가지고 있는 최소단위는 음소나 글자 하나가 아닌 '단어'이기 때문이다.
그렇다면 전산학의 입장에서 왜 이러한 객체지향 관점이 주목을 받게 된 것일까? 여러 가지 이유가 있지만, 가장 중요한 것은 뭐니뭐니해도 '소프트웨어의 개발효율과 재사용'에 있다. 즉, 하드웨어를 다뤄본 분들은 알겠지만, 회로를 설계할 때 가능하면 이미 만들어져 있는 chip(IC : Integrated Circuit)을 잘 골라서 군데군데 박아놓고, 주로 그것들의 연결(인터페이스)에 주목을 한다. 왜냐하면 회로를 만들 때마다 RLC(저항, 코일, 콘덴서)와 다이오드(가장 기본적인 반도체 소자)만으로 처음부터 일일이 만들려고 하면, 아무리 실력이 있는 사람이라도 라디오 하나, 전자시계 하나도 제대로 만들어 내기 힘들 뿐만 아니라 만드는 데에도 너무 오래 걸리고, 디버깅도 잘 안 되
는 등 문제가 이만저만하지 않을 것이다.
우리가 IC를 한 번 만들기는 어렵지만, 일단 잘 만들어 놓기만 하면 여기저기에 쓰이듯이, 거대한 프로젝트도 항상 바닥에서부터 장기계획으로 만들려 하지 말고, 이미 만들어져 있는 객체들의 조합으로 만들자는 것이다. 물론, 아직 만들어지지는 않았지만 필요한 객체는 직접 만들어야 한다. 대신, 다음부터는 그 객체를 활용할 수 있다. 물론, 잘 만들었을 경우의 얘기지만 말이다.
우리가 회로를 만들 때 사용하는 IC를 일일이 분해하여 그 내부를 현미경으로 들여다보지 않듯이 객체 역시 안을 보지 말아야 한다. 이것을 정보은폐(Information Hiding)라고 하는데, 비자금을 숨기는 것처럼 사람들에게는 알려할 정보를 은폐시킨다는 것이 아니고, 보일 정보만 보이고 보이면 오히려 개발자를 혼동시킬 정보는 볼 수 없게끔 한다는 것이다. 개발자는 단지 그 객체가 어떤 기능을 가지고 있고, 어떻게 사용할 수 있는지에 대해서만 알면 된다는 것이다. 그런 것들까지 신경 쓰지 않더라도 프로그램을 만들려면 신경써야 할 것이 한 두 개가 아니라는 것이다. 이러한 소프트웨어의 모듈화는 사실 OOP가 처음은 아니며 구조적 프로그래밍에서도 강조하는 것인데,
OOP는 아예 언어에서 이러한 것들을 지원하고 제한하여서 표준을 유지한다는 것이 차이점 중의 하나이다.
컴퓨터에 있어서의 객체는 크게 두 가지가 있다. 기본객체(primitive object)와 복합객체(complex object)이다. 객체지향의 관점에서는 정수(integer)나 실수(float)도 객체이다. 어떤 것이 객체인지의 여부는 그것이 데이터와 그것에 작용하는 연산이 함께 있으면 독립적인 객체이다. 정수(integer)의 경우에도 정수를 저장하기 위한 4바이트의 메모리와 그것에 작용을 하는 가감승제 등의 연산이 있기 때문에 객체이다. 정수나 실수와 같이 소프트웨어의 기본이 되는 객체들을 기본객체(Primitve Object)라 한다면 이러한 원시객체들을 구성요소로 하여 만들어진 보다 복잡한 객체들을 복합객체(complex object)라고 부른다.
예를 들어 복소수의 경우에는 기존의 C 언어에 없었던 데이터형이므로 복합객체(complex object)이다. 이것의 멤버 데이터는 실수부(real part)와 허수부(imaginary part)의 숫자이며 멤버함수는 복소수가 할 수 있는 연산이다. 복합객체를 발전시키면 객체를 사람의 일반적인 사고방식에 가깝게 개념적으로 설계할 수 있게 된다. 이것이 OOP의 중요한 특징 중의 하나인 추상 자료형(abstract data type)인 것이다. 예를 들어 윈도우라는 객체가 있으면, 이 객체가 구체적으로 어떻게 만들어지고 어떤 방식으로 동작하는가를 전산학적인 관점에서 일일이 검토하는 것이 아니라, 마치 윈도우가 우리의 실세계의 객체인 것처럼, 우리는 단지 윈도우 객체에게 '열려라', '닫혀라' 등의 우리 식의 메시지를 보내면 메시지를 받은 윈도우 객체는 '알아서' 그 동작을 하는 것이다.
객체들끼리는 크게 두 가지의 관계성이 있는데, 그러한 관계들로 인하여 계층적 구조를 형성하고 있다. 관계성 중의 하나는 IS_A 관계성인데, 이것은 '사자 is a 동물', '호랑이 is a 동물'과 같은 관계성을 나타내며 동물이라는 클래스는 사자와 호랑이의 공통 부모 클래스로서 사자와 호랑이에게 공통적으로 있는 성질을 가지고 있다. 이때 사자 클래스와 호랑이 클래스는 동물 클래스가 가지고 있는 모든 기능을 물려받아서 마치 자신의 것처럼 활용할 수 있는데, 이것을 OOP 용어로 상속(inheritance)이라고 한다. 이 상속성은 객체지향 설계에서 매우 중요한 역할을 하는데, 우리는 객체들끼리의 IS_A 관계성을 이용하여 우리가 만들려는 클래스를 맨땅에서부터 시작하는 것
이 아니라, 마치 부모에게 유산을 물려받아 그것을 기본으로 사업을 벌이듯이 기존의 클래스 중의 하나를 부모 클래스로 하여 상속 받아서 만들 수 있는 것이다. 여기서 클래스라는 용어가 처음 나왔는데, 객체와 클래스는 어떻게 다른 것인지를 알아보기로 하자.
광일이는 사람이다. 그리고 진호도 사람이다. 즉, 광일이와 진호는 사람이라는 클래스에 속하는 구체적이고 개별적으로 유일하게 하나만 존재하는 객체인 것이다. '광일'과 '진호'는 사람이라는 클래스에 맞도록 생성된 객체를 구별해 주는 객체의 이름(Object Identifier)이다. 다시 말하면, 클래스는 붕어빵 들을 말하며 붕어빵 틀에 의해 그 모양대로 생겨나고 없어지는 붕어빵은 객체이다. 한편 바로 위에서 말한 사자와 호랑이는 객체의 이름이 아니라 동물군에 속하는 또 다른 클래스의 이름이다. 우리는 호랑이라는 클래스를 이용하여 우리가 필요한 만큼의 '호돌이', '호순이', '호순이' 객체를 생성시키고 소멸시킬 수 있는 것이다.
객체들끼리의 또 다른 관계성은 HAS_PARTS_OF이다. 하나의 '방(room)'이라는 클래스를 설계한다고 생각하자. 이 때, 우리는 '방'을 한 번에 만들어 내는 것보다는 이 '방'이 어떤 구성요소와 어떤 더 작은 객체들의 모임인지를 생각해 보는 것이 중요하다. 일반적으로 방에는 '문'이 있다. 그런데 이 '문'이라는 것도 객체이다. 다시 말하면 '방' has parts of '문'의 관계가 있다. 또한, '방' has parts of '창'의 관계도 있다. 이 때, 문과 창은 방을 구성하는 부품으로서의 객체이다. 그러므로 우리는 가능하면 다른 사람이 이미 만들어놓은 문이나 창이 있는지를 살펴보고, 만일 있다면 우리가 방을 만들 때 그것을 쓸 수가 있다. 또한, 문과 창이 아직 없다면 그것을 따로 만들어서 방 이외의 객체를 만들 때 사용할 수 있을 것이다.
앞에서 거론한 바와 같이 객체는 그것의 데이터와 그 데이터에 대한 연산(method)으로 구성이 되어 있다. 여기서는 C++ 언어에서 이러한 객체를 어떻게 명세(specify)하는지 그 문법을 통하여 알아보도록 하자. 이것은 C++로 프로그램을 짤 때 항상 기본적으로 외우고 있어야 하는 것이다.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Class 객체이름 : Public 부모 객체이름
private:
프라이비트 속성의 멤버 데이터;
프라이비트 속성의 멤버함수;
public:
퍼블릭 속송의 멤버 데이터;
퍼블릭 속성의 멤버함수;
}
객체이름 :: 멤버함수{
....(멤버함수의 기능을 코딩) ...
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
어떤 객체의 멤버 데이터나 멤버함수는 private나 public이나 protected 중의 하나의 속성을 가져야 하며, 이것은 클래스의 설계자가 정한다. 이것이 바로 클래스의 정보은폐(Information Hiding)를 지원하기 위해 C++에서 제공하는 키워드이다. private는 말 그대로 사적인 것이므로 자기 자신에게만 있고 아무도 접근(access)할 수 없는 멤버임을 말한다. public은 반대로 다른 객체에게 '보이고', 다른 객체가 접근할 수 있는 멤버를 말한다. 마지막으로 protected는 다른 객체에게는 private로 작동하지만 자신의 자식 객체에게는 public한 멤버를 말한다.
여기서, 다른 객체에게 '보인다'라는 말이 어떤 의미인지 짚고 넘어가자, 우리말이 원래 그러하듯이 여기서의 '보인다'라는 말은 실제로 그 함수가 프로그램의 소스에서 글자가 보이느냐 안 보이느냐를 말하는 것이 아니다. 아무리 소스상에서는 보이지만, 다른 클래스에서 그것을 콜(Call)하려고 하면, 컴파일러는 그런 함수가 어디 있느냐는 듯이 에러를 내면서 컴파일되지 않는 것을 말한다. 바로 클래스의 설계자가 그 클래스를 사용하려는 개발자에게 사용법의 제약을 일부러 준 것이다.
그렇다고 해서 클래스를 자기가 만들어 자기가 그 클래스를 사용하려 할 때 '보이지 않으니까 불편하므로' private 속성을 마구 Public으로 바꾼다면 그것은 더 이상 OOP가 아니다. 왜 C++를 써야 하는지도 모르면서 남들이 쓰니까 써야 되는 줄 알고 쓰는, 소위 개념도 없는 사람들이 그러한 행동을 하는데, 그러다 보면 성수대교처럼 프로그램이 처음에는 동작하는 듯하다가 갑자기 붕괴하면서 OS까지 다시 깔아야 되는 경우가 생길 수도 있으므로 조심하여야 한다.
DOS에서는 프로그램을 잘 짰었는데, 윈도우 프로그래밍으로 넘어가면서 도저히 적응을 못하고 프로그래머를 포기하려는 사람들이 가장 어려워하고 이해가 되지 않는 것이 윈도우 프로그램의 Message-Driven 방식이다. 물론 DOS에서도 볼랜드 C++ 3.1을 이용하여 C++로 프로그램을 짤 수도 있었지만, 우리가 도스시절 프로그램을 설계하고 구현하던 대부분의 방식은 구조화 프로그램이다. 구조화 프로그래밍에서는 큰 프로그램을 계속 쪼개어서 작은 함수들의 조각들로 만들고, 그 조각들도 제한된 몇 가지만의 제어구조(순방향처리, 반복처리, 조건처리)만으로 구현했었다. 이 때는 프로그래머가 커다란 지도를 잘게 나눈 후, 그 조각을 돋보기로 보듯이 실행하는 순서(flow)가 정확하게 존재했었다.
이런 방식에 길들여진 기존의 프로그래머들이 윈도우 프로그램에서는 '어떤 순서로 프로그램이 돌아가는 것일까'에 지나치게 집착하다보면, 프로그램의 흐름이 도대체 한 개인지 여러 개인지, 그리고 이것이 어디로 떠났다가 어디서 돌아오는 것인지 도무지 알 수 없어서 한 방 맞은 후, 자신의 프로그램에서 어떤 기능을 어느 부분에 코딩해야 될 지에 자신 없어 한다. 또한, 그러다보니 디버깅도 어떻게 해야 할지를 모르게되고, 주위 사람들에게 너무 어렵다고 협박을 하면서 포기를 한다.
그러나 이런 문제의 답은 간단하다. '더 이상 프로그램의 흐름(flow)을 생각하지 말라.' 물론, CPU가 순차적으로 동작하는 한, 분명히 flow는 존재할 것이다. 하지만 이제 컴퓨터는 매우 거대한 체계의 소프트웨어 구조로서 움직인다. 우리는 별 생각없이 자연스럽게 모니터의 한쪽에 TV를 켜두고 워드를 치면서 통신 에뮬레이터를 작동시키지만, 바로 이러한 멀티태스킹을 구축하기 위해 OS는 매우 복잡해지고 방대해졌다. 과거에는 미니급 컴퓨터 이상의 슈퍼컴퓨터에서 쓰이던 OS 이론이 이제 윈도우에도 대부분 적용되게 되었다.
그러므로 자신이 OS나 컴파일러를 직접 만들지도 않았으면서, MFC로 만든 애플리케이션을 어떻게 어떤 순서로 돌아가는지를 일일이 자기가 짠 것만큼 속속들이 알려고 하는 것은 거의 무모한 것이다. 우리가 MFC로 윈도우 프로그램을 만든다는 것은 MFC라는 인터페이스를 사용하는 것이며, 우리는 반드시 MFC에서 정의해 놓은 정보를 가지고, MFC를 만든 사람들이 정해 놓은 대로 짜야만 하는 것이다.
안타까운 현실이지만, 날로 거대해져만 가는 소프트 산업에 의해 어쩔 수 없는 일이다. 물론, 윈도우의 내부구조와 MFC의 설계이념 등을 자세히 알 수록 우리는 맘껏 프로그램을 짤 수 있다. 단지, DOS에서처럼 만만치가 않고 참으로 많은 공부들을 깊이 있게 해야만 가능하다는 것이다.
그렇다면 더 이상 flow(코드의 실행순서)의 의미가 없어진 OOP에서 우리는 어떻게 프로그램을 코딩할 수 있을까? 해답은 '메시지'에 있다. OOP의 객체들은 메시지에 의해 반응하면서, 각자가 자기의 일을 분주하게 하고 있는 가상적인 '사회'와 같다. 우리는 OOP로 프로그램을 설계할 때 가장 먼저 그 프로그램에 어떠한 객체들이 있어야 하는지, 그 객체들은 서로 어떤 관계성을 가지는지, 각각의 객체들은 어떤 시점에서 어떤 메시지에 반응하여 어떤 동작을 해주어야 하는지에 주안점을 두어서 설계하여야 하는 것이다. 그러고 나서 우리는 이미 달리기 시작한 개썰매에 탄 주인처럼 각각 자기 나름대로 달리고 있는 개들에게 채찍이라는 메시지를 이용하여 적절한 시기에 적절한 메시지를 해당 객체에게 보내주는 방식으로 개썰매를 가려고 하는 방향으로 잘 조종할 수 있는 것이다. 결코 개 한마리가 한 발자국 움직인 다음, 어떤 개가 다음 한 발자국을 떼어야 할지를 고민하고 있어서는 안 된다.
OOP는 이런 것들 외에도 프로그램을 보다 인간이 사고하는 방식에 가깝게 코딩하여서, 개발과 분석이 용이하도록 여러 가지를 지원한다. 예를 들면 다형성(Polymorphism)과 연산자 오버로딩(operator overloading)이 있다. 이러한 특성은 개체를 설계하는 개발자나 그 객체를 사용하려는 개발자 모두에게 프로그래밍 문제를 객체지향적이고 개념적으로 분석할 때, 의사소통이 잘 되도록 하는데 그 목적이 있다. 하지만 이러한 기능을 잘못 남발하면 오히려 서로에게 더 정신 없고 애매모호한 프로그램이 될 수도 있다. 프로그램은 뭐니뭐니해도 그 구조와 알고리즘이 깨끗하고 명확하여야 한다는 것을 명심해 두어야 한다.
이후에는 OOP 전용의 언어보다는 기존 언어에 OOP 개념을 집어넣은 언어들이 많이 나왔다. 인공지능용 언어인 LISP도 지금은 OOP 개념을 집어넣은 언어들이 많이 나왔다. 인공지능용 언어인 LISP도 지금은 OOP 개념을 확장하여 ObjectLISP가 되었으며, 여러분이 잘 아는 Delphi에서 사용하는 언어도 그냥 파스칼이 아니라 ObjectPASCAL이다. 또 포트란도 ObjectFORTRAN이 되었다. 하지만 필자는 뭐니뭐니해도 현재는 C++를 잘 하는 것이 가장 중요한 시점이라고 생각한다.
객체(Object)란 무엇인가?
OOP의 핵심적인 용어는 '객체'이다. OOP는 쉽게 말하면 모든 것을 객체로 본다는 관점이다. 즉, 윈도우도 객체이고, 버튼도 객체이다. 글자도 객체이고, 사운도도 객체인 것이다. 객체는 우리가 무엇을 생각할 때 '개념적인 최소단위'라고 정의한다. 그러므로 일반적으로 우리가 단어를 붙일 수 있는 것은 거의 대부분 객체라고 할 수 있다. 왜냐하면 언어에서 개념적인 최소단위는, 즉 독립적인 의미를 가지고 있는 최소단위는 음소나 글자 하나가 아닌 '단어'이기 때문이다.
그렇다면 전산학의 입장에서 왜 이러한 객체지향 관점이 주목을 받게 된 것일까? 여러 가지 이유가 있지만, 가장 중요한 것은 뭐니뭐니해도 '소프트웨어의 개발효율과 재사용'에 있다. 즉, 하드웨어를 다뤄본 분들은 알겠지만, 회로를 설계할 때 가능하면 이미 만들어져 있는 chip(IC : Integrated Circuit)을 잘 골라서 군데군데 박아놓고, 주로 그것들의 연결(인터페이스)에 주목을 한다. 왜냐하면 회로를 만들 때마다 RLC(저항, 코일, 콘덴서)와 다이오드(가장 기본적인 반도체 소자)만으로 처음부터 일일이 만들려고 하면, 아무리 실력이 있는 사람이라도 라디오 하나, 전자시계 하나도 제대로 만들어 내기 힘들 뿐만 아니라 만드는 데에도 너무 오래 걸리고, 디버깅도 잘 안 되
는 등 문제가 이만저만하지 않을 것이다.
우리가 IC를 한 번 만들기는 어렵지만, 일단 잘 만들어 놓기만 하면 여기저기에 쓰이듯이, 거대한 프로젝트도 항상 바닥에서부터 장기계획으로 만들려 하지 말고, 이미 만들어져 있는 객체들의 조합으로 만들자는 것이다. 물론, 아직 만들어지지는 않았지만 필요한 객체는 직접 만들어야 한다. 대신, 다음부터는 그 객체를 활용할 수 있다. 물론, 잘 만들었을 경우의 얘기지만 말이다.
우리가 회로를 만들 때 사용하는 IC를 일일이 분해하여 그 내부를 현미경으로 들여다보지 않듯이 객체 역시 안을 보지 말아야 한다. 이것을 정보은폐(Information Hiding)라고 하는데, 비자금을 숨기는 것처럼 사람들에게는 알려할 정보를 은폐시킨다는 것이 아니고, 보일 정보만 보이고 보이면 오히려 개발자를 혼동시킬 정보는 볼 수 없게끔 한다는 것이다. 개발자는 단지 그 객체가 어떤 기능을 가지고 있고, 어떻게 사용할 수 있는지에 대해서만 알면 된다는 것이다. 그런 것들까지 신경 쓰지 않더라도 프로그램을 만들려면 신경써야 할 것이 한 두 개가 아니라는 것이다. 이러한 소프트웨어의 모듈화는 사실 OOP가 처음은 아니며 구조적 프로그래밍에서도 강조하는 것인데,
OOP는 아예 언어에서 이러한 것들을 지원하고 제한하여서 표준을 유지한다는 것이 차이점 중의 하나이다.
컴퓨터에 있어서의 객체는 크게 두 가지가 있다. 기본객체(primitive object)와 복합객체(complex object)이다. 객체지향의 관점에서는 정수(integer)나 실수(float)도 객체이다. 어떤 것이 객체인지의 여부는 그것이 데이터와 그것에 작용하는 연산이 함께 있으면 독립적인 객체이다. 정수(integer)의 경우에도 정수를 저장하기 위한 4바이트의 메모리와 그것에 작용을 하는 가감승제 등의 연산이 있기 때문에 객체이다. 정수나 실수와 같이 소프트웨어의 기본이 되는 객체들을 기본객체(Primitve Object)라 한다면 이러한 원시객체들을 구성요소로 하여 만들어진 보다 복잡한 객체들을 복합객체(complex object)라고 부른다.
예를 들어 복소수의 경우에는 기존의 C 언어에 없었던 데이터형이므로 복합객체(complex object)이다. 이것의 멤버 데이터는 실수부(real part)와 허수부(imaginary part)의 숫자이며 멤버함수는 복소수가 할 수 있는 연산이다. 복합객체를 발전시키면 객체를 사람의 일반적인 사고방식에 가깝게 개념적으로 설계할 수 있게 된다. 이것이 OOP의 중요한 특징 중의 하나인 추상 자료형(abstract data type)인 것이다. 예를 들어 윈도우라는 객체가 있으면, 이 객체가 구체적으로 어떻게 만들어지고 어떤 방식으로 동작하는가를 전산학적인 관점에서 일일이 검토하는 것이 아니라, 마치 윈도우가 우리의 실세계의 객체인 것처럼, 우리는 단지 윈도우 객체에게 '열려라', '닫혀라' 등의 우리 식의 메시지를 보내면 메시지를 받은 윈도우 객체는 '알아서' 그 동작을 하는 것이다.
객체들끼리는 크게 두 가지의 관계성이 있는데, 그러한 관계들로 인하여 계층적 구조를 형성하고 있다. 관계성 중의 하나는 IS_A 관계성인데, 이것은 '사자 is a 동물', '호랑이 is a 동물'과 같은 관계성을 나타내며 동물이라는 클래스는 사자와 호랑이의 공통 부모 클래스로서 사자와 호랑이에게 공통적으로 있는 성질을 가지고 있다. 이때 사자 클래스와 호랑이 클래스는 동물 클래스가 가지고 있는 모든 기능을 물려받아서 마치 자신의 것처럼 활용할 수 있는데, 이것을 OOP 용어로 상속(inheritance)이라고 한다. 이 상속성은 객체지향 설계에서 매우 중요한 역할을 하는데, 우리는 객체들끼리의 IS_A 관계성을 이용하여 우리가 만들려는 클래스를 맨땅에서부터 시작하는 것
이 아니라, 마치 부모에게 유산을 물려받아 그것을 기본으로 사업을 벌이듯이 기존의 클래스 중의 하나를 부모 클래스로 하여 상속 받아서 만들 수 있는 것이다. 여기서 클래스라는 용어가 처음 나왔는데, 객체와 클래스는 어떻게 다른 것인지를 알아보기로 하자.
광일이는 사람이다. 그리고 진호도 사람이다. 즉, 광일이와 진호는 사람이라는 클래스에 속하는 구체적이고 개별적으로 유일하게 하나만 존재하는 객체인 것이다. '광일'과 '진호'는 사람이라는 클래스에 맞도록 생성된 객체를 구별해 주는 객체의 이름(Object Identifier)이다. 다시 말하면, 클래스는 붕어빵 들을 말하며 붕어빵 틀에 의해 그 모양대로 생겨나고 없어지는 붕어빵은 객체이다. 한편 바로 위에서 말한 사자와 호랑이는 객체의 이름이 아니라 동물군에 속하는 또 다른 클래스의 이름이다. 우리는 호랑이라는 클래스를 이용하여 우리가 필요한 만큼의 '호돌이', '호순이', '호순이' 객체를 생성시키고 소멸시킬 수 있는 것이다.
객체들끼리의 또 다른 관계성은 HAS_PARTS_OF이다. 하나의 '방(room)'이라는 클래스를 설계한다고 생각하자. 이 때, 우리는 '방'을 한 번에 만들어 내는 것보다는 이 '방'이 어떤 구성요소와 어떤 더 작은 객체들의 모임인지를 생각해 보는 것이 중요하다. 일반적으로 방에는 '문'이 있다. 그런데 이 '문'이라는 것도 객체이다. 다시 말하면 '방' has parts of '문'의 관계가 있다. 또한, '방' has parts of '창'의 관계도 있다. 이 때, 문과 창은 방을 구성하는 부품으로서의 객체이다. 그러므로 우리는 가능하면 다른 사람이 이미 만들어놓은 문이나 창이 있는지를 살펴보고, 만일 있다면 우리가 방을 만들 때 그것을 쓸 수가 있다. 또한, 문과 창이 아직 없다면 그것을 따로 만들어서 방 이외의 객체를 만들 때 사용할 수 있을 것이다.
앞에서 거론한 바와 같이 객체는 그것의 데이터와 그 데이터에 대한 연산(method)으로 구성이 되어 있다. 여기서는 C++ 언어에서 이러한 객체를 어떻게 명세(specify)하는지 그 문법을 통하여 알아보도록 하자. 이것은 C++로 프로그램을 짤 때 항상 기본적으로 외우고 있어야 하는 것이다.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Class 객체이름 : Public 부모 객체이름
private:
프라이비트 속성의 멤버 데이터;
프라이비트 속성의 멤버함수;
public:
퍼블릭 속송의 멤버 데이터;
퍼블릭 속성의 멤버함수;
}
객체이름 :: 멤버함수{
....(멤버함수의 기능을 코딩) ...
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
어떤 객체의 멤버 데이터나 멤버함수는 private나 public이나 protected 중의 하나의 속성을 가져야 하며, 이것은 클래스의 설계자가 정한다. 이것이 바로 클래스의 정보은폐(Information Hiding)를 지원하기 위해 C++에서 제공하는 키워드이다. private는 말 그대로 사적인 것이므로 자기 자신에게만 있고 아무도 접근(access)할 수 없는 멤버임을 말한다. public은 반대로 다른 객체에게 '보이고', 다른 객체가 접근할 수 있는 멤버를 말한다. 마지막으로 protected는 다른 객체에게는 private로 작동하지만 자신의 자식 객체에게는 public한 멤버를 말한다.
여기서, 다른 객체에게 '보인다'라는 말이 어떤 의미인지 짚고 넘어가자, 우리말이 원래 그러하듯이 여기서의 '보인다'라는 말은 실제로 그 함수가 프로그램의 소스에서 글자가 보이느냐 안 보이느냐를 말하는 것이 아니다. 아무리 소스상에서는 보이지만, 다른 클래스에서 그것을 콜(Call)하려고 하면, 컴파일러는 그런 함수가 어디 있느냐는 듯이 에러를 내면서 컴파일되지 않는 것을 말한다. 바로 클래스의 설계자가 그 클래스를 사용하려는 개발자에게 사용법의 제약을 일부러 준 것이다.
그렇다고 해서 클래스를 자기가 만들어 자기가 그 클래스를 사용하려 할 때 '보이지 않으니까 불편하므로' private 속성을 마구 Public으로 바꾼다면 그것은 더 이상 OOP가 아니다. 왜 C++를 써야 하는지도 모르면서 남들이 쓰니까 써야 되는 줄 알고 쓰는, 소위 개념도 없는 사람들이 그러한 행동을 하는데, 그러다 보면 성수대교처럼 프로그램이 처음에는 동작하는 듯하다가 갑자기 붕괴하면서 OS까지 다시 깔아야 되는 경우가 생길 수도 있으므로 조심하여야 한다.
DOS에서는 프로그램을 잘 짰었는데, 윈도우 프로그래밍으로 넘어가면서 도저히 적응을 못하고 프로그래머를 포기하려는 사람들이 가장 어려워하고 이해가 되지 않는 것이 윈도우 프로그램의 Message-Driven 방식이다. 물론 DOS에서도 볼랜드 C++ 3.1을 이용하여 C++로 프로그램을 짤 수도 있었지만, 우리가 도스시절 프로그램을 설계하고 구현하던 대부분의 방식은 구조화 프로그램이다. 구조화 프로그래밍에서는 큰 프로그램을 계속 쪼개어서 작은 함수들의 조각들로 만들고, 그 조각들도 제한된 몇 가지만의 제어구조(순방향처리, 반복처리, 조건처리)만으로 구현했었다. 이 때는 프로그래머가 커다란 지도를 잘게 나눈 후, 그 조각을 돋보기로 보듯이 실행하는 순서(flow)가 정확하게 존재했었다.
이런 방식에 길들여진 기존의 프로그래머들이 윈도우 프로그램에서는 '어떤 순서로 프로그램이 돌아가는 것일까'에 지나치게 집착하다보면, 프로그램의 흐름이 도대체 한 개인지 여러 개인지, 그리고 이것이 어디로 떠났다가 어디서 돌아오는 것인지 도무지 알 수 없어서 한 방 맞은 후, 자신의 프로그램에서 어떤 기능을 어느 부분에 코딩해야 될 지에 자신 없어 한다. 또한, 그러다보니 디버깅도 어떻게 해야 할지를 모르게되고, 주위 사람들에게 너무 어렵다고 협박을 하면서 포기를 한다.
그러나 이런 문제의 답은 간단하다. '더 이상 프로그램의 흐름(flow)을 생각하지 말라.' 물론, CPU가 순차적으로 동작하는 한, 분명히 flow는 존재할 것이다. 하지만 이제 컴퓨터는 매우 거대한 체계의 소프트웨어 구조로서 움직인다. 우리는 별 생각없이 자연스럽게 모니터의 한쪽에 TV를 켜두고 워드를 치면서 통신 에뮬레이터를 작동시키지만, 바로 이러한 멀티태스킹을 구축하기 위해 OS는 매우 복잡해지고 방대해졌다. 과거에는 미니급 컴퓨터 이상의 슈퍼컴퓨터에서 쓰이던 OS 이론이 이제 윈도우에도 대부분 적용되게 되었다.
그러므로 자신이 OS나 컴파일러를 직접 만들지도 않았으면서, MFC로 만든 애플리케이션을 어떻게 어떤 순서로 돌아가는지를 일일이 자기가 짠 것만큼 속속들이 알려고 하는 것은 거의 무모한 것이다. 우리가 MFC로 윈도우 프로그램을 만든다는 것은 MFC라는 인터페이스를 사용하는 것이며, 우리는 반드시 MFC에서 정의해 놓은 정보를 가지고, MFC를 만든 사람들이 정해 놓은 대로 짜야만 하는 것이다.
안타까운 현실이지만, 날로 거대해져만 가는 소프트 산업에 의해 어쩔 수 없는 일이다. 물론, 윈도우의 내부구조와 MFC의 설계이념 등을 자세히 알 수록 우리는 맘껏 프로그램을 짤 수 있다. 단지, DOS에서처럼 만만치가 않고 참으로 많은 공부들을 깊이 있게 해야만 가능하다는 것이다.
그렇다면 더 이상 flow(코드의 실행순서)의 의미가 없어진 OOP에서 우리는 어떻게 프로그램을 코딩할 수 있을까? 해답은 '메시지'에 있다. OOP의 객체들은 메시지에 의해 반응하면서, 각자가 자기의 일을 분주하게 하고 있는 가상적인 '사회'와 같다. 우리는 OOP로 프로그램을 설계할 때 가장 먼저 그 프로그램에 어떠한 객체들이 있어야 하는지, 그 객체들은 서로 어떤 관계성을 가지는지, 각각의 객체들은 어떤 시점에서 어떤 메시지에 반응하여 어떤 동작을 해주어야 하는지에 주안점을 두어서 설계하여야 하는 것이다. 그러고 나서 우리는 이미 달리기 시작한 개썰매에 탄 주인처럼 각각 자기 나름대로 달리고 있는 개들에게 채찍이라는 메시지를 이용하여 적절한 시기에 적절한 메시지를 해당 객체에게 보내주는 방식으로 개썰매를 가려고 하는 방향으로 잘 조종할 수 있는 것이다. 결코 개 한마리가 한 발자국 움직인 다음, 어떤 개가 다음 한 발자국을 떼어야 할지를 고민하고 있어서는 안 된다.
OOP는 이런 것들 외에도 프로그램을 보다 인간이 사고하는 방식에 가깝게 코딩하여서, 개발과 분석이 용이하도록 여러 가지를 지원한다. 예를 들면 다형성(Polymorphism)과 연산자 오버로딩(operator overloading)이 있다. 이러한 특성은 개체를 설계하는 개발자나 그 객체를 사용하려는 개발자 모두에게 프로그래밍 문제를 객체지향적이고 개념적으로 분석할 때, 의사소통이 잘 되도록 하는데 그 목적이 있다. 하지만 이러한 기능을 잘못 남발하면 오히려 서로에게 더 정신 없고 애매모호한 프로그램이 될 수도 있다. 프로그램은 뭐니뭐니해도 그 구조와 알고리즘이 깨끗하고 명확하여야 한다는 것을 명심해 두어야 한다.
댓글 없음:
댓글 쓰기