객체 지향 프로그래밍
객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 컴퓨터 프로그래밍의 패러다임 중 하나이다.
- 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 "객체"들의 모임으로 파악하고자 하는 것이다. ▶ 객체: 하나의 역할을 수행하는 '메소드와 변수(데이터)'의 묶음
- 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다.
절차적 프로그래밍
- Procedure-Oriented Programming (POP)
- 프로시저 호출의 개념을 바탕으로 하고 있는 프로그래밍 패러다임의 일종
- 프로시저: 루틴, 하위프로그램, 서브루틴, 메서드, 함수라고도 하며, 수행되어야 할 연속적인 계산 과정을 포함한다.
- 프로그램의 아무 위치에서나 프로시저를 호출할 수 있고, 다른 프로시저에서도 호출이 가능하다. 심지어는 자기 자신에서도 호출이 가능하다.
- 절차적 프로그래밍을 따르는 대표적인 언어로는 C, Fotran 등이 있다.
장점
- 반복적인 동작을 함수로 구현하기 때문에 코드의 재활용성이 높고, 코드의 길이가 줄어든다.
- 함수의 호출을 통해 여러 부분을 생략하여 프로그램의 흐름을 쉽게 파악할 수 있어 코드의 가독성이 높아진다.
단점
- 코드 간 유기성이 높아 유지보수가 어렵다. ▶ 조금만 복잡해져도 "스파게티 코드"가 될 가능성이 높음
- 실행 순서가 정해져 있어 코드 순서가 바뀌면 동일한 결과를 보장하기 어렵다.
- 디버깅이 어렵다.
객체 지향 프로그래밍
- Object-Oriented Programming (OOP)
- 객체라는 작은 단위에서 프로그램이라는 큰 단위로 나아가는 Bottom-up 방식 (상향식)
- 객체를 하나의 레고처럼 각각을 끼워 맞추며 프로그램을 만든다고 생각하면 된다.
- 대표적인 언어로는 C++, Python, Java 등이 있다.
장점
- 객체간 독립성이 높다. (dependecy가 낮다.) ▶ 각 객체를 독립적으로 운용할 수 있어 유지보수가 용이하다.
- 각 기능을 모듈화함으로써 재활용성이 높아진다.
- 불필요한 연산을 줄이고 하드웨어의 연산 처리량을 획기적으로 줄일 수 있다.
- 대형 프로젝트에 적합한 프로그래밍이다. 업무를 분담하기에 알맞다.
- 디버깅이 쉽다.
단점
- 객체가 많아지고, 코드가 길어질수록 용량이 매우 커지고, 프로그램 속도도 느려진다.
- 절차적 프로그래밍보다 상대적으로 설계 시간이 오래 걸린다.
절차적 프로그래밍 vs 객체 지향 프로그래밍
- 절차적 프로그래밍과 객체 지향 프로그래밍은 상반된 개념이 아니다.
- 절차적이지 않은 프로그래밍은 없다.
- 절차적 프로그래밍은 "절차 = 프로시저"라는 기능 단위로 진행하는 방식이며, 객체 지향 프로그래밍은 "객체"라는 단위로 진행되는 방식이다. ▶ POP - 절차, 순서에 집중. OOP - 객체들의 속성, 종류 등에 집중
절차적 프로그래밍(POP) | 객체 지향 프로그래밍(OOP) | |
접근 방식 | Top-Down | Bottom-Up |
구성 요소 | 기능(Function/Procedure) | 객체(Object) |
접근 제한자 | X (모두 Public) | Public, Protected, Private |
다형성 | X | O |
상속 | X | O (Public, Protected, Private) |
보안 | 낮음 | 높음 |
Friend 기능 | X | C++에 있음 |
가상 클래스 or 함수 | X | 상속 하에 있음 |
코드 재활용 | X | O |
장점 | 코드 작성이 쉬운 편 | 코드를 효율적으로 작성 |
단점 | 규모가 큰 프로젝트에서는 코드가 복잡해지고 중복될 가능성이 높음 | 객체가 많아질수록 오버헤드가 늘어 상대적으로 자원과 시간을 많이 소비 |
대표 언어 | C, VB, FORTRAN, Pascal | C++, JAVA, VB.NET, C#.NET |
객체 지향 프로그래밍의 특징
- OOP 특징은 대표적으로 추상화, 캡슐화, 상속, 다형성 총 4가지를 설명한다.
1. 추상화 (abstraction)
- 객체들의 공통적인 특징(기능, 속성)을 추려내는 것
- 불필요한 부분을 생략하고 객체의 속성 중 중요한 것에만 중점을 두어 개략화한다.
- 추상화를 이용하면 코드의 재사용성, 가독성이 높아지고, 유지보수에 있어 많은 시간을 절약할 수 있다.
- 객체 지향적 관점에서는 클래스를 정의하는 것을 추상화라고 할 수 있다.
예시
- 위는 대표적인 IT 업계들인 네카라쿠배당토이다.
- 네이버, 카카오, 라인 등을 각각 객체라고 할 수 있으며, 이들을 대형 IT 기업이라는 큰 틀로 묶을 수 있겠다.
- 그리고 대형 IT 기업이라는 큰 틀(클래스)에서 프로그래밍, B2C 서비스, 이직이 쉬움 등의 공통적인 특징을 정의할 수 있다. 이렇게 공통적인 특징을 추려내는 과정이 추상화이다.
- 추상화를 완료하면, 큰 틀(클래스)을 기준으로 각 객체가 갖는 개별적인 특징만 정의하면 된다. (ex. 네이버 - 웹엔진, 당근마켓 - 소셜 커머스, 토스 - 금융 등)
2. 캡슐화 (Encapsulation)
- 객체의 데이터와 기능을 하나로 묶고 외부에 노출되지 않도록 은닉하는 것
- 모듈 내 높은 응집도, 모듈 간 낮은 결합력
- 변수, 메소드, 클래스에 대해 접근 제어자를 사용하여 캡슐화할 수 있다.
- 외부에서 객체의 속성 및 메소드에 접근할 수 없게 하여, 외부에서의 잘못된 접근과 조작으로 인한 객체 손상을 방지할 수 있다.
예시
- 자동차는 가속 페달을 밟으면 차의 속도가 빨라지고, 브레이크 페달을 밟으면 속도가 느려진다.
- 우리는 가속 페달을 밟으면 왜 빨라지는지, 브레이크 페달을 밟으면 왜 느려지는지 그 이유를 알지 못한다. 그렇지만 그 사실을 알지 못하더라도 우리는 자동차를 운전할 수 있다.
- 그런데 갑자기 굳이 내부적인 구조가 궁금해서 자동차 차체를 뜯어 보았고, 이후 다시 조립하는 과정에서 가속 페달과 브레이크 페달의 연결선을 반대로 끼워 버렸다.
- 이제부터는 브레이크 페달을 밟으면 속도가 빨라지고, 가속 페달을 밟으면 속도가 느려진다.
- 이를 원상 복구 하기가 매우 어렵고 귀찮아서 그대로 운전을 했고, 결국 멈춰야 할 때 차를 가속시키는 실수를 하여 교통사고를 발생시켰다.
- 자동차 회사 측은 이러한 황당한 짓을 하는 사람을 보고는 절대로 차체를 뜯어볼 수 없도록 막아버렸고, 누구도 가속 페달과 브레이크 페달의 기능을 뒤바꾸어 버리는 실수를 하지 않게 되었다.
- 이러한 회사 측의 행동은 객체의 손상을 막기 위한 캡슐화 과정이라고 볼 수 있겠다.
3. 상속 (inheritance)
- 상위 개체의 특성(속성, 기능)을 하위 개체가 그대로 물려받는 것
- 이미 작성된 클래스를 그대로 재사용하여 새로운 클래스를 만드는 것이다.
- 모든 기능을 그대로 사용하지 않고, 일부를 수정하여 다시 정의할 수 있다. ▶ 오버라이딩(overriding)
- 상속은 OOP의 특징인 캡슐화를 유지하면서도 클래스의 재사용을 용이하게 해준다.
예시
- Animal은 동물이라는 최상위 개념이며, 살아 움직이고 뇌가 있고, 다리가 있다는 특성을 갖는다.
- Animal이라는 상위 개념에서 Human, Dog라는 하위 개념을 정의할 수 있다. Human과 Dog는 상위 개념인 Amimal의 특성을 그대로 가지면서도 각 객체의 고유한 특성 또한 갖고 있다.
- 다시 Dog의 하위 개념으로 말티즈와 웰시코기를 정의할 수 있다. 말티즈와 웰시코기 또한 상위 개념인 Dog의 특성을 가지면서도 각 객체의 고유한 특성을 갖고 있다.
- 예시처럼 상위 개념의 특성을 하위 개념이 물려 받는 것을 상속이라고 한다.
상속의 장점
- 객체를 만들어 놓고 언제든지 다시 사용할 수 있어 재사용성이 높아진다.
- 상위 클래스를 그대로 사용하기 때문에 하위 클래스를 만드는 데 걸리는 시간이 단축된다.
- 하위 클래스를 일일이 수정할 필요 없이 상위 클래스를 수정하면 되기 때문에 유지보수가 용이하다.
상속의 단점
- 상위 클래스의 내부 구현이 달라지면 하위 클래스에 영향을 끼친다. 따라서 모든 하위 클래스의 기능 또한 수정해야 할 가능성이 있다. ▶ 하위 클래스가 상위 클래스에 대해 강한 결합력을 갖고, 객체 내에서는 약한 응집력을 가지는 수동적인 객체가 됨.
- 하위 클래스의 기능 확장을 위해, 상위 클래스의 정보를 파헤쳐 보안 허점이 발생할 수 있다.
- 클래스의 기능을 재사용하기 위한 목적으로 상속하는 경우, 엉뚱한 is-a 관계로 인해 의도치 않은 동작이 이루어질 수 있다.
상속 대신 조합(Composition)
- 조합(Composition): 기존 클래스가 새로운 클래스를 구성하기 위한 요소로 쓰임. ▶ 레고 조립을 생각
- is-a 관계(상속): 사람은 동물이다. / 사과는 과일이다. / 자동차는 이동수단이다. 등..
- has-a 관계(조합): 사람은 눈이 있다. / 컴퓨터는 연산장치를 가진다. / 자동차는 엔진을 가진다. 등..
- 조합을 사용하는 경우, 하위클래스는 상위 클래스를 메소드로서 호출하기 때문에 캡슐화를 유지하면서도, 의존적이지 않게 되므로 상위 클래스의 변화가 끼치는 영향이 줄어든다.
상속은 언제 사용해야 하는가?
- 반드시 조합을 사용하는 것이 능사는 아니다. 특정 상황에서는 상속이 더욱 강력하고 편리하게 사용된다.
- 1. is-a 관계가 명확할 때 사용한다.
- 2. 코드의 재사용 보다는 기능 확장의 관점에서 상속을 사용한다.
- 3. 상위 클래스의 메소드를 절대로 바꾸지 않을 것이라고 생각될 때 사용한다.
4. 다형성 (Polymorphism)
- 상속받은 메소드나 클래스가 서로 다른 클래스에서 다양한 방식으로 동작하는 것
- 다형성을 통해 확장성과 유연성이 높아진다.
- 대표적으로 오버로딩(overloading), 오버라이딩(overriding)이 있다.
예시
- 다형성을 나타내는 하나의 예시이다.
- Dog 클래스에는 Bark()라는 메소드가 있다. (짖다)
- 말티즈와 웰시코기는 Dog의 하위 클래스이기 때문에 Bark() 메소드를 사용할 수 있다.
- 이 메소드를 각각의 객체에서 실행했을 때, 개마다 소리가 다르기 때문에 말티즈는 Yip!! 하면서 짖고, 웰시코기는 Woof!! 하면서 짖는다.
오버로딩(overloading)
- 함수명이 같은 함수(메소드)를 중복 정의하는 것
- 매개변수 자료형과 개수에 따라 동일한 이름을 갖는 여러 개의 함수를 만들 수 있다.
- 다음은 같은 이름(sum)을 갖는 함수를 중복한 오버로딩 예시이다.
#include <iostream>
using namespace std;
class Math {
public:
int sum(int a, int b) {
cout << "정수의 합: ";
return a + b;
}
float sum(float a, float b) {
cout << "실수의 합: ";
return a + b;
}
};
void main() {
Math math;
cout << math.sum(3, 5) << endl;
cout << math.sum(3.6f, 3.7f) << endl;
}
정수의 합: 8
실수의 합: 7.3
오버라이딩(overriding)
- 상속받은 부모 클래스의 메소드를 그대로 사용하지 않고 자식클래스에서 기능을 재정의하여 사용하는 것
- 오버라이딩은 다음을 준수하여야 한다.
- 상위 클래스에 오버라이드하려는 메소드가 반드시 있어야 함
- 메소드 명이 같아야 함
- 메소드의 매개변수의 개수 및 자료형이 동일해야 함
- 메소드의 반환형이 같아야 함
- 다음은 하위 클래스에서 상위 클래스의 메소드(Bark)를 재정의한 오버라이딩 예시이다.
#include <iostream>
using namespace std;
class Dog {
public:
void Bark(int a) {
for (int i = 0; i < a; i++) {
cout << "Bow-wow!!" << " ";
}
cout << endl;
}
};
class Maltese : public Dog {
public:
void Bark(int a) {
for (int i = 0; i < a; i++) {
cout << "Yip!!" << " ";
}
cout << endl;
}
};
void main() {
Dog dog;
Maltese maltese;
dog.Bark(3);
maltese.Bark(3);
}
Bow-wow!! Bow-wow!! Bow-wow!!
Yip!! Yip!! Yip!!
더보기
SOLID
객체 지향 5원칙