객체지향 프로그래밍의 이해


C++은 객체지향 언어이다. 따라서 객체지향에 대한 이해가 필요하다.


객체는 영어로 Object 이다 그리고 이의 사전적인 의미는 "사물 또는 대상" 을 의미한다.


즉 Object는 우리 주변에 존재하는 물건이나 대상 전부를 의미한다. 그렇다면 객체를 지향하는 프로그래밍이라는 것은 무었일까? 예를 들어서 다음 상황을 시뮬레이션 하는 프로그램을 구현한다고 가정해보자.


" 나는 과일장수에게 두개의 사과를 구매했다! "


이 문장에 삽입되어 있는 객체의 종류는 다음과 같다.


나, 과일장수, 사과


그렇다면 '나' 라는 객체는 '과일장수' 라는 객체로부터 '과일' 객체의 구매라는 액션을 취할 수 있어야 한다. 그런대 객체지향 프로그래밍에서는 '나' 그리고 '과일장수'와 같은 객체를 등장시킬 수 있을 뿐만 아니라. '나'라는 객체가 '과일장수' 라는 객체로부터 '과일' 객체를 구매하는 행위도 그대로 표현할 수 있다. 즉, 객체지향 프로그래밍은 현실에 존재하는 사물과 대상, 그리고 그에 따른 해동을 있는 그대로 실체화 시키는 형태의 프로그래밍이다. 이의 확인을 위해서 '나'와 '과일장수'라는 객체를 생성하여 다음의 행동을 실체화시켜 보겠다.


" 나는 과일장수에게 2,000원을 주고 두개의 사과를 구매했다. "


참고로 사과도 객체로 실체화시킬 수 있으나, 

코드의 간결성을 위해서 '나' 와 '과일장수'만 객체화 시키도록 하겠다.



객체를 이루는 것은 데이터와 기능이다.


프로그램상에 과일장수 객체가 존재한다고 가정해보자. 이 객체는 무엇으로 이뤄져야 할까?

프로그램상에서 바라보는 과일장수의 관점은 '과일의 판매'에 있다. 

따라서 프로그램상에서 바라보는 과일장수는 다음과 같은 형태이다,


  • 과일장수는 과일을 판다.
  • 과일장수는 사과 20개 오랜지 10개를 보유하고 있다.
  • 과일장수의 과일판매 수익은 현재까지 50000원이다.

이중 첫 번째는 과일장수의 '행동(behavior)' 을 의미한다. 그리고 두 번째와 세 번째는 과일 장수의 '상태(state)' 를 의미한다. 이처럼 객체는 하나 이상의 상태 정보(Data)와 하나 이상의 행동(기능)으로 구성되며, 상태 정보는 변수를 통해 표현이 되고, 행동은 함수를 통해서 표현이 된다. 먼저 과일장수의 상태 정보를 변수로 표현해보자.


  • 보유하고 있는 사과의 수         -> int numOfApples ;
  • 판매 수익                            -> int myMoney ;
이번에는 과일장수의 행위인 과일의 판매를 함수로 표현해보겠다.

  1. int SaleApples(int money)    // 사과 구매액이 함수의 인자로 전달   
  2. {  
  3.    int num = money/1000;     // 사과가 개당 1000원이라고 가정      
  4.    numOfApples -= num;       // 사과의 수가 줄어들고,  
  5.    myMoney += money;         // 판매 수익이 발생한다.  
  6.    return num;               // 실제 구매가 발생환 사과의 수를 반환.  
  7. }  


이렇게 해서 과일장수 객체를 구성하게 되는 변수와 함수가 마련되었다.

이제 이들을 묶어서 구체화 해보자.



'과일장수'의 정의와 멤버변수의 상수화에 대한 논의


객체를 생성하기에 앞서 객체의 생성을 위한 틀을 먼저 만들어야 한다.이는 현실세계에서 물건을 만들기 위한 틀을 짜는 행위에 비유할 수 있다. '나' 또는 '과일장수' 객체를 생성하기 위해서는 이 둘을 위한 틀을 먼저 만들어야 하는데 위에서 마련해 놓은 함수와 변수를 이용해서 틀을 만들면 다음과 같이 만들 수 있다.


  1. class FruitSeller  
  2. {  
  3. private:  
  4.     int APPLE_PRICE;  
  5.     int numOfApples;  
  6.     int myMoney;  
  7.   
  8. public:  
  9.     int SaleApples(int money)  
  10.     {  
  11.         int num = money / APPLE_PRICE;  
  12.         numOfApples -= num;  
  13.         myMoney += money;  
  14.         return num;  
  15.     }  
  16. };  


그렇다! 클래스의 정의이다.

즉 FruitSeller라는 이름의 클래스가 과일장수의 틀이 되는 것이다.


그런데 우리는 과일장수에게 다음과 같은 질문을 할 수도 있다.


"오늘 과일좀 많이 파셨나요?"


그러면 과일장수는 다음과 같이 대답을 할 것이다.


"2000원 벌었어, 남는 사과는 18개고 말이야!"


그래서 이러한 대화에 사용되는 함수와 변수를 초기화 하는데 사용하는 함수를 추가해서 

과일장수의 틀을 다음과 같이 완성할 수 있다,


  1. class FruitSeller  
  2. {  
  3. private:  
  4.     int APPLE_PRICE;  
  5.     int numOfApples;  
  6.     int myMoney;  
  7.   
  8. public:  
  9.     void InitMembers(int price, int num, int money)  
  10.     {  
  11.         APPLE_PRICE = price;  
  12.         numOfApples = num;  
  13.         myMoney = money;  
  14.     }  
  15.   
  16.     int SaleApples(int money)  
  17.     {  
  18.         int num = money / APPLE_PRICE;  
  19.         numOfApples -= num;  
  20.         myMoney += money;  
  21.         return num;  
  22.     }  
  23.   
  24.     void ShowSalesResult()  
  25.     {  
  26.         cout << "remnantal apple : " << numOfApples << endl;  
  27.         cout << "sales profit    : " << myMoney << endl;  
  28.     }  
  29. };  


'나' 를 표현하는 클래스의 정의


이제 '나'를 표현하기 위한 클래스를 정의할 차례이다. 

그렇다면 나라는 클래스에는 어떠한 변수들과 함수들로 채워져야 할까?


먼저 데이터적인 측면을 바라보자.

구매자에게 있어서 가장 중요한 것은 돈이다. 돈이 있어야 구매가 가능하기 때문이다. 그리고 구매를 했다면 해당 물품을 소유하게 된다. 따라서 두 변수를 클래스의 멤버변수로 생각해 볼 수 있다.


  • 소유하고 있는 현금의 액수       -> int myMoney ;
  • 소유하고 있는 사과의 개수       -> int numOfApples ;

이제 기능적 측면을 생각해보자. 과일 구매자가 지녀야 할 기능은 과일의 구매이다.
따라서 이 기능을 담당할 함수를 클래스에 추가해야 한다.
이 함수의 이름은 BuyApple이라 하면 나의 클래스는 다음과 같이 정의할 수 있다.

  1. class FruitBuyer  
  2. {  
  3.     int myMoney;        // private :  
  4.     int numOfApples;    // private :  
  5.   
  6. public:  
  7.     void InitMembers(int money)  
  8.     {  
  9.         myMoney = money;  
  10.         numOfApples = 0;  
  11.     }  
  12.     void BuyApples(FruitSeller &seller, int money)  
  13.     {  
  14.         numOfApples += seller.SaleApples(money);  
  15.         myMoney -= money;  
  16.     }  
  17.     void ShowBuyResult()  
  18.     {  
  19.         cout << "My Money     : " << myMoney << endl;  
  20.         cout << "num of Apple : " << numOfApples << endl;  
  21.     }  
  22. };  

위의 클래스에 선언된 두 변수 myMoney와 numOfApples에는 private나 public과 같은 선언이 존재하지 않음을 알 수 있다. 그러나 클래스는 아무런 선언이 존재하지 않을 때 private로 간주된다.(구조체는 public) private 선언을 목적으로 접근제어 지시자를 생략하는 경우가 흔히 있으니 기억해두자.


클래스 기반의 두 가지 객체생성 방법

우리는 두개의 클래스를 정의하였다. 그렇다면 객체를 생성하지 않고 이 두 클래스 안에 존재하는 변수에 접근하고 함수를 호출하는 것이 가능할까? 결론부터 말하면 불가능하다, 이들은 실체(객체) 가 아닌 틀이다. 따라서 앞서 정의한 클래스를 실체화 시키는 작업이 필요하다. 즉 객체화 하는 것이다. C++에는 두가지 객체생성 방법이 존재한다. 이는 기본 자료형 변수의 선언방식과 동일함을 보여준다.


  • ClassName objName ;                               // 일반적인 변수의 선언방식
  • ClassName *ptrObj = new ClassName ;         // 동적 할당방식(힙 할당방식)

즉 우리가 정의한 FruitSeller 클래스와 FruitBuyer 클래스의 객체 생성방식은 다음과 같다.

  • FruitSeller seller ;
  • FruitBuyer buyer ;
그리고 이를 동적으로 할당하려면 다음과 같이 생성하면 된다.

  • FruitSeller *objPtr1 = new FruitSeller ; 
  • FruitBuyer *objPtr2 = new FruitBuyer ;


사과장수 시뮬레이션


예제를 완성해보자 이 예제가 의미를 갖는 이유는 두 객체가 서로 대화를 하기 때문이다.

그럼 객체는 어떻게 대화를 주고 받는지 예제를 통해 확인해보자.


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class FruitSeller  
  5. {  
  6.     int APPLE_PRICE;  
  7.     int numOfApples;  
  8.     int myMoney;  
  9.   
  10. public:  
  11.     void InitMembers(int price, int num, int money)  
  12.     {  
  13.         APPLE_PRICE = price;  
  14.         numOfApples = num;  
  15.         myMoney = money;  
  16.     }  
  17.     int SaleApples(int money)  
  18.     {  
  19.         int num = money/APPLE_PRICE;  
  20.         numOfApples -= num;  
  21.         myMoney += money;  
  22.         return num;  
  23.     }  
  24.     void ShowSalesResult()  
  25.     {  
  26.         cout << "remnantal apple : " << numOfApples << endl;  
  27.         cout << "sales profit    : " << myMoney << endl << endl;  
  28.     }  
  29. };  
  30.   
  31. class FruitBuyer  
  32. {  
  33.     int myMoney;  
  34.     int numOfApples;  
  35.   
  36. public:  
  37.     void InitMembers(int money)  
  38.     {  
  39.         myMoney = money;  
  40.         numOfApples = 0;  
  41.     }  
  42.     void BuyApples(FruitSeller &seller, int money)  
  43.     {  
  44.         numOfApples += seller.SaleApples(money);  
  45.         myMoney -= money;  
  46.     }  
  47.     void ShowBuyResult()  
  48.     {  
  49.         cout << "My money     : " << myMoney << endl;  
  50.         cout << "num of Apple : " << numOfApples << endl << endl;  
  51.     }  
  52. };  
  53.   
  54. int main()  
  55. {  
  56.     FruitSeller seller;  
  57.     seller.InitMembers(1000, 20, 0);  
  58.     FruitBuyer buyer;  
  59.     buyer.InitMembers(5000);  
  60.     buyer.BuyApples(seller, 2000);  
  61.   
  62.     cout << "Result of Seller" << endl;  
  63.     seller.ShowSalesResult();  
  64.     cout << "Result of Buyer" << endl;  
  65.     buyer.ShowBuyResult();  
  66.     return 0;  
  67. }  



[ 결과 ]



 

  • 60행 : 이 예제에서 어렵게 느껴질 수 있는 부분이다. BuyApples 은 사과의 구매 기능을 담당하는 함수이다. 즉 이 함수 내에서 사과의 구매가 완성되어야 한다. 그렇다면 생각해 보자. 사과를 구매하는데 있어서 필요한 것 두가지가 무엇인가? 그것은 바로 구매대상과 구매금액이다, 그래서 이 둘의 정보가 인자로 전달되도록 함수가 정의되었다.

위 예제에서 44행과 60행을 특히 주목해서 보자. 이는 코드의 흐름 이상의 것을 담고 있기 때문에 자세히 볼 필요가 있다. 이어서 이 부분에 대한 설명을 추가로 진행하도록 하겠다.


객체간의 대화 방법(Message Passing)


위 예제 44행을 보면 FruitBuyer 객체에 존재하는 함수 내에서 FruitSeller 객체의 함수 SaleApples를 호출하고 있다. 그런데 이 한문장은 현실세계에서 다음과 같이 표현할 수 있다.


" seller 님 사과 2000원어치 주세요 "


'나'라는 객체가 '과일장수'라는 객체로부터 '과일' 객체를 구매하는 행위도 그대로 표현할 수 있다.

즉 44행은 FruitBuyer 객체가 FruitSeller 객체에게 위와 같은 메시지를 전달하는 상황이다.


이처럼 하나의 객체가 다른 하나의 객체에게 메시지를 전달하는 방법은(어떠한 행위의 요구를 위한 메시지 전달) 함수호출을 기반으로 한다. 

그러나 객체지향에서는 이러한 형태의 함수호출을 가리켜 '메시지 전달(Message Passing)' 이라 한다.

'Language > C++' 카테고리의 다른 글

C++ 기본(5) 클래스의 완성  (0) 2017.01.04
C++ 기본(3) 클래스(Class)와 객체(Object)  (0) 2016.12.28
C++ 기본(2) - 구조체  (0) 2016.12.23
8. C++ 기본(8)  (0) 2016.12.14
7. C++ 기본(7)  (0) 2016.12.13

+ Recent posts