본문 바로가기

별걸다하는 IT/프로그래밍언어

[C/C++] 절차지향언어와 객체지향언어의 관점 차이, 절차지향 프로그래밍과 객체지향 프로그래밍은 무엇일까요

[C / C++ 프로그램 강좌 목차]

안녕하세요 주인장 양햄찌입니다.

클래스 관련 포스팅을 들어가기 앞서, 절차지향 언어와 객체지향 언어가 무엇인가에 대해 정리를 해보려고 해요.

 

대게 C언어는 절차지향언어야!, C++은 객체지향 언어야! 이렇게 표현을 하죠.

절차지향이 무엇이고 객체지향이 무엇일까요??

절차지향 프로그래밍이란? Procedural Oriented Programming (POP)

 

사진출처: https://hackr.io/blog/procedural-programming

 

- '프로그램은 무슨 일을 하는가'의 관점으로 바라본 프로그래밍 기법

- 순차적으로 진행 (TOP-DOWN 접근방식)

- 기능을 하나의 함수로 구현

 

과~~거에는 지금처럼 프로그램의 크기가 크지 않았어요.

그러니까 사람들은 하나의 프로그램을 짤 때, 기능을 중심으로 생각을 했죠. 

 

제가 만약 여러분에게 코드로 계산기 프로그램을 짜오세요 하고 과제를 내주면

여러분은 음, 계산기라면 덧셈기능, 곱셈기능, 나눗셈기능을 만들어야겠군.

그리고 +버튼이 들어오면 덧셈함수를 호출시켜 실행하고, - 버튼이 들어오면 뺄셈 함수를 호출시켜 실행해야겠다.

이런식으로 그 프로그램이 수행할 일을 먼저 생각하잖아요.

 

이처럼 '프로그램은 무슨 일을 하는가?'라는 사고에서 비롯된 개념이 절차지향 방식입니다.

사람들은 프로그램을 짤 때 기능을 하나의 함수로 구현했고, 프로그램은 구현하고자 하는 일의 순서에 따라 main부터 차례 차례 코드를 실행하고 함수를 호출하는 형태를 갖추게 됩니다. 이렇게 기능은 함수로 구현하고 프로그램 시작부터 순차적으로 코드를 실행하는 방식을 절차지향이라고 해요.

 

간단한 계산기 코드

 

앞에서 예로 들었던 계산기를 간략하게 코드로 짜보자면 위처럼 되겠죠? 간단하게 짠거라 걍 int값만 있다고 가정하에 짰습니다.

 

즉 일정한 단계에 따라 진행하는 프로그램을 작성할 때는 절차형 접근 방식이 적합한거죠.

절차지향은 Procedural Programming이라고 합니다.

위에서 아래로 실행되기 때문에 TOP-DOWN 접근방식을 취한다고 해요.

대표적인 절차지향 언어로는 우리가 지금 배우고 있는 C언어가 있어요 ㅎㅎ

 

객체지향 프로그래밍이란? Object Oriented Programming (OOP) 

절차지향 프로그래밍과 비교하면서 이해해보자.

 

- '현실세계의 어떤 대상을 모델링 하는가'의 관점으로 바라본 프로그래밍 기법

- 세부모델부터 디자인되어 조립되는 BOTTOM UP 접근 방식

- 함수보다는 데이터를 표현하는 객체 중심

 

그럼 객체지향은 무엇일까요? 

오늘날 프로그램 크기가 점점 커지고 과거보다 더욱더 개인정보가 중시되었죠.

또 이제 대규모 어플리케이션 같은 경우에는 여러 이벤트가 랜덤적으로 발생하기도 해요.

 

이 때문에 절차지향에서는 '기능' 중심이었다면, 객체지향에서는 '데이터'가 중심이 되고, 

데이터를 정보보안적으로, 보이지 않게 묶어서, 서로 유기적으로 동작하게 모델링 하도록 할순 없을까 한거죠.

 

'현실세계에의 어떤 대상을 모델링 하는가?'라는 관점에서 비롯된게 객체지향 방식입니다.

 

 

사진출처:https://www.quora.com/To-a-beginner-how-would-you-explain-the-difference-between-object-oriented-functional-and-procedural-programming

 

잘 나와있는 그림이 있길래 첨부해봤습니다.

절차지향 중심 프로그래밍, 함수로부터 데이터를 받아서 기능을 구현하는 방식으로 진행,

객체지향 중심 프로그래밍 경우에는 객체에 데이터와 기능이 존재하고 객체들이 서로 유기적으로 동작하는 주체가 됩니다.

 

절차지향에서도 계산기를 예시로 들었으니, 동일하게 계산기로 예로 들자면,

객체지향에서는 계산기를 큰 프로그램에서 하나의 데이터로 봅니다.

계산기가 있으면 사실 공학용 계산기도 있고, 날짜 계산기가 있고, 프로그래머 계산기가 있고, 많잖아요?

예전에야 그냥 간단하게 덧셈, 뺄셈, 곱셈 등 몇가지만 있어도 되었다면 현시대는 그렇지 않죠~

이렇게 프로그램이 확장될수록 기능으로 구현했던게, 하나의 데이터로 묶여야할 필요성이 나타난거예요.

int add (int a, int b)
//-->더한다는게 중심! a와 b를 더한다!

calculator.add()
//-->행위주체자가 계산기라는 객체! 즉 계산기가 더하는 동작을 수행한다. 객체가 주체가 되는 문장.
//-->값은 계산기라는 객체가 가지고 있는 속성이 됨!

기존 절차지향에서는 함수가 주체! 함수에다가 서로 연산하고자 하는 값을 넣어줬다면 (함수와 데이터는 별개)

객체지향에서는 객체가 주체! calculator는 add 동작, minus 동작, 등을 가지게 되는 겁니다. (타입에 종속적)

 

만약 공학용계산기, 계산기, 날짜 계산기 등을 절차지향적으로 프로그래밍 한다고 가정하면..?

#include <stdio.h>
#include <stdlib.h>
#define STANDARD_TYPE 0x01
#define DATE_TYPE 0x02
#define SCIENTIFIC_TYPE 0x04
#define TYPE_ON(x, y) (x)|=(y) 
#define HAS_TYPE(x, y) x & y ? 1:0

float add(float a, float b);
float minus(float a, float b);
float multiple(float a, float b);
float divide(float  a, float b);
int modulus(int a, int b);
//많은 functions.. (함수 중심으로 구현되어 있다.)
int main()
{
	int type = 0, a = 0, b = 0;
	char oper, calculator_type = 0x00; 
	scanf("%d", &type);
	scanf("%c", &oper);
	scanf("%d %d", &a, &b);
	//만약 공학용 계산기이면
	if (type == 1)
	{
		TYPE_ON(calculator_type, SCIENTIFIC_TYPE);
		TYPE_ON(calculator_type, STANDARD_TYPE);
	}

	if (HAS_TYPE(calculator_type, STANDARD_TYPE))
	{
		printf("일반 계산기 기능\n");
		switch (oper)
		{
		case '+': add(a, b); break;
		case '-': minus(a, b); break;
		case '*': multiple(a, b); break;
		default: printf("not supported function\n"); exit(100);
		}
	}
	if (HAS_TYPE(calculator_type, SCIENTIFIC_TYPE))
	{
		printf("공학용 계산기 기능\n");
		//공학용 계산기에 들어가는 추가 기능 등등
	}
	if (HAS_TYPE(calculator_type, DATE_TYPE))
	{
		printf("날짜 계산기 기능\n");
		//등등
	} 
	//..중략 
	return 0;
}
//중략

대략 이런 형식을 뛰겠죠? ( 일단 생각나는 대로 짠거라 더 좋은 코드가 있을 수 있어요 )

여기서 중요한 것은 절차지향은, 데이터보다는 해당 프로그램이 하고자하는 목적을 중심으로 짜여진 것! (절차지향)

#include <stdio.h>
#include <stdlib.h>
typedef struct _Calculator  //일반 계산기
{
	int (*add)(int, int);	//함수포인터 변수 
	int (*minus)(int, int);
	int (*multiple)(int, int);
} Calculator;
typedef struct _ScientificCalculator  //공학용계산기 
{
	int (*add)(int, int);		//일반계산기 연산에 있는거 중복
	int (*minus)(int, int);		//일반계산기 연산에 있는거 중복
	int (*multiple)(int, int);	//일반계산기 연산에 있는거 중복
	//추가적인 공학용 계산기 연산
	int (*modulas)(int, int);
	//funcionst 등등 
}ScientificCalculator;

int add(int a, int b);
int minus(int a, int b);
int multiple(int a, int b);
int modulus(int a, int b);
//많은 functions..

int main()
{
	Calculator cal{};
	cal.add = add;			//함수포인터 변수와 함수와 연결하는 작업 필요
	cal.minus = minus;
	cal.multiple = multiple;
    //일반계산기에 없는 공학용 계산기 함수를 일반계산기에 연결지어도 방어가 되지 않음 
	cal.multiple = modulus; 
    
	printf("cal.modulas %d\n", cal.add(10 , 20 )); //구조체 내의 함수포인터 변수 사용 
	printf("add result %d", add(4, 30)); //그냥 함수로도 접근 가능 (종속되지 않음을 확인) 
	
	return 0;
}

물론 C언어에서도 위처럼 struct를 이용해서 계산기를 하나의 데이터로 묶을 수 있습니다.

class Calculator 하듯이 struct Calculator 할 수 있는거죠.

 

또 클래스가 멤버함수를 가지듯이

구조체에서도 함수를 함수 포인터를 이용해 멤버변수로 넣을 수는 있어요.

(하지만 본래 C언어 구조체는 함수를 가질 수 없습니다. 그러니까 함수를 변수 형태인 함수포인터 변수로 멤버변수에 넣어, 함수를 호출할 수 있게끔 조작(?)한거겠죠..?!)

 

하지만 이렇게 구현하게 되면

일반 계산기 구조체, 공학용계산기 구조체, 날짜 계산기 구조체 등 새로운 유형이 나올 때마다 매번 새로운 구조체 전체를 정의해줘야합니다. 모든 계산기가 가지고 있는 공통 기능도 다 중복으로 넣어줘야해요...!,

 

1. printf("cal.modulas %d\n", cal.add(10 , 20 )); //구조체 내의 함수포인터 변수 사용 
2. printf("add result %d", add(4, 30)); //그냥 함수로도 접근 가능 (종속되지 않음을 확인) 

여기서 구조체와 add라는 함수는 독립된 존재라는 것!

add는 일반용계산기에서도 호출될 수 있고 공학용계산기에서도 호출될 수 있고, 굳이 구조체 아닌 일반 함수로써도 내가 사용할 수 있는 그런, 즉 데이터에 종속되지 않은 그런 함수인거죠. 

그래서 1번 처럼 구조체 객체가 add를 호출하는 주체가 될 수도 있지만,

2번 처럼 그냥 함수로써 호출도 가능해요. == 누구나 호출 가능하다 == 특정 데이터에 종속되어 있지 않다.

 

1.	Calculator cal{};
2.	cal.add = add;			//함수포인터 변수와 함수와 연결하는 작업 필요
3.	cal.minus = minus;
4.	cal.multiple = multiple;
    //일반계산기에 없는 공학용 계산기 함수를 일반게산기에 연결지어도 방어가 되지 않음 
5.	cal.multiple = modulus; 

또 함수 포인터 변수와 함수를 연결해주는 작업도 필요해요. (2~5라인)

클래스가 가지고 있는 접근 제한기능이 없기 때문에

5번 라인처럼 multiple은 일반 계산기에서 호출할 수 있고

modulus 기능은 일반 계산기가 아닌 공학용 계산기에서만 호출 할 수 있는 기능인데

일반 계산기 multiple 함수포인터에다가 modulus함수를 연결지어도 문제가 되지 않음.. 호출 시키킬 수 있게 침범(?)할 수가 있어요. 영역기능이 없는 문제인거죠.

 

[객체지향 코드로 작성]

만약 해당 코드를 객체지향적으로 대략 작성해본다면,

#include <iostream>
using namespace std;
class Calculator //데이터
{
private:
	int a;
	int b;
public:
	Calculator(int a, int b);
	int add();
	int minus();
	int multiple();
};
class ScientificCalculator : public Calculator //데이터
{
	int a;
	int b;
public:
	ScientificCalculator(int a, int b);
	int modular();
};
int main()
{
	ScientificCalculator cal(10, 3);
	cout << cal.add() << endl;  //데이터동작
	cout << cal.modular() << endl;  //데이터동작
    // add(3, 5) --> add함수는 클래스에 종속되어 있기 때문에 일반 함수로 호출 불가
	return 0;
}
//중략

요렇게 구성해볼 수 있습니다. 특징은, 데이터 중심이예요. 계산기라는 클래스를 잡아줬고, 공학용 계산기는 일반 계산기 기능을 물려받을 수 있게 했습니다. 그리고 add는 Calculator라는 객체에 종속되어 있는 함수입니다.

따라서 그냥 add(3, 5)이렇게 호출할 수 없어요. 

차이점이 보이시나요?

 

또 체스프로그램을 간단하게 예로 들어본다면,

절차지향방식에서는 int chess[10][10] 이런식으로,  

객체지향 방식에서는 class chess { int row, int height } 이런식으로,

객체지향에서는 데이터가 얽매이지 않게 좀 더 표현 방식에 중점을 둔 것을 생각해볼 수 있습니다.

Bottum up 방식

객체지향 접근 방식은 Bottom up 방식이라고 해요. 

 

 

차를 설계한다 하면, 차의 부품인 바퀴, 핸들, 차체 등.. 이렇게 데이터를 먼저 구성하고, 차의 바퀴를 교체한다, 앞으로 나아간다 등의 유기적으로 동작할 수 있게끔 프로그램이 짜여집니다.

그런데 운송수단에는 차 뿐만 아니라 자전거도 있죠. 그럼 공통적인 것들을 '운송수단' 이렇게 클래스로 묶어서, 차와 자전거에 위 사진처럼 운동수단을 상속받게 함으로써 공통 속성을 부여해줄 수 있습니다. 우리가 공학용 계산기에 공통 계산기 기능을 부여해버린 것처럼요~.(C언어는 상속개념 없음. 객체지향에 생긴 개념)

이렇게 세부 모델부터 디자인 후 조립되는 이런 방식을 Bottup up 접근방식이라 합니다.

 

객체 모델링

 

사진출처: http://newnazirite.com/newnaz2015/?p=126

 

데이터를 묶는 기준은 개발자가 판단합니다.

객체를 만드는 클래스는 표현하고자 하는 데이터가 가지고 있는 속성과 동작을 중심으로 모델링됩니다. 

게임용 캐릭터를 만든다 하면, 절차지향에서는 해당 프로그램의 목적! 캐릭터의 목적. 즉 move, hit, healing, run

등의 기능을 함수로 구현하고 인자로 대상을 넘겨준다면,

객체지향에서는 캐릭터에 종속된 변수를 속성으로, (ex 체력, 마나, 힘, 현재속도 등)

멤버함수에는 동작을 나타내는 기능을 넣어 하나의 데이터로 구성합니다 (ex 때리다, 달리다, 걷다, 치유하다 등)

 

어디까지를 종속시킬 것이냐에 대한 부분은 개발하는 자의 판단이기 때문에~!!! 이 부분에 대해 어떻게 설계할 것인가가 중요합니다.

굳이 클래스로 묶을 필요가 없는데 극단적으로 OOP로만 구성하려는 것 또한 좋은 코드가 아니죠.

절차지향 방식과 객체지향 방식은 서로 반대되는 관점이 아닙니다. 다른 관점이예요.

구현하고자 하는 시스템에 따라 이를 적절히 섞을 줄 아는 것 또한 좋은 역량이 되겠죠?

POP VS OOP 비교표

간단하게 표로 정리해봤어요.

비교항목 POP OOP
의미 절차지향 방식 객체지향 방식
접근방식 Top-down Bottum-up
분할 큰 프로그램은 함수라는 유닛들로 구성됩니다. 프로그램은 객체들로 구성됩니다.
접근모드 특별한 접근제한 지정자가 없음. 접근 제한 지정자가 있음 (private, protected, public 등)
오버로딩/다형성 없음 가능
상속 없음 가능
데이터 은닉 없음 가능
데이터 공유 프로그램에서 함수들이 데이터공유 가능 객체간 멤버 함수들끼리 공유 
friend 없음 가능 (C++)

가상클래스
없음 가능
예시 언어 C, 비주얼베이직, 포트란, 파스칼 C++, 자바, VB.NET, C#.NET.

객체지향 특징

객체지향에는 크게 4가지 특징이 있습니다.

1. 정보은닉.

2. 캡슐화.

3. 상속.

4. 다형성

 

앞으로 클래스라는 개념에 대해 배우면서 

클래스는 어떤 특징이 있길래 해당 부분을 충족할 수 있는지에 대해 배우게 될거예요.

 

▼상속이란?

https://jhnyang.tistory.com/73

 

[C++, java 언어공통]상속을 언제, 왜 쓸까?(inheritance, Is-A)

[C언어, C++언어 완전 정복! 강의 목차 링크] 상황으로 상속 한번에 이해하기 자 우리가 메이플스토리 게임을 만들거예요 아주 대강~~~ 으로요 일단 메이플스토리 캐릭터들을 만들어봅시다. 음 마�

jhnyang.tistory.com

 

오늘은 간단하게 관점 차이에 대해서만 서술했습니다.

도움이 되었다면 공감 어떤가요?! 다음 포스팅에서 뵐게요.