객체 지향 프로그래밍
절차적 프로그래밍
객체 지향 프로그래밍을 알아보기 전에 이전에 사용되었던 절차적 프로그래밍에 대해 알아보자
절차적 프로그래밍이란 순차적으로 프로그래밍을 하는 방식이다.
순서대로 차곡차곡 쌓아 올리는 건축물과 비슷한 느낌으로 이해할 수 있다.
int add(int a, int b);
int minus(int a, int b);
int multiple(int a, int b);
int divide(int a, int b);
int main(){
add(1, 2);
minus(4, 3);
multiple(5, 6);
divide(8, 3);
}
객체 지향 프로그래밍
객체 지향 프로그래밍은 "객체"를 기능 단위로 하는 프로그래밍 방식이다.
객체는 메소드(함수), 데이터(변수)가 모두 포함되며 특정 역할을 수행하도록 정의한 추상적인 개념이다.
모든 부품들을 각각 제작하여 만든 조립식 건물과 비슷한 느낌으로 이해할 수 있다.
자세히 이해하기 위하여 객체 지향 프로그래밍의 특징 4가지에 대해 알아보자.
캡슐화
캡슐화는 외부에서 그 내부를 볼 수 없도록 데이터를 숨기는 작업이다.
물론 데이터를 숨겨 접근할 수 없는 것은 아니고, 외부로부터 데이터를 조작할 인터페이스는 필요하다.
#include <iostream>
using namespace std;
class Person {
private:
string name; // private: 외부에서 직접 접근 불가능
public:
void setName(string newName) { // public 메서드를 통해 값 설정
name = newName;
}
string getName() { // public 메서드를 통해 값 조회
return name;
}
};
int main() {
Person person;
person.setName("Alice");
cout << "Name: " << person.getName() << endl;
return 0;
}
위 코드를 보면 main 함수에서 Person 클래스의 name 변수가 캡슐화되어 main 함수에서는 접근할 수 없게 되었다.
하지만 setName()과 getName()을 통해 안전하게 name을 설정하거나 가져올 수 있다.
상속
상속은 부모(기본) 클래스의 속성과 기능을 자식(파생) 클래스에서 재사용하는 작업이다.
이를 통해 클래스들이 같은 속성을 가지고 있을 때 이를 모두 정의해줄 필요 없이, 부모 클래스에서 이를 정의하고 부모로부터 기능을 상속받아 사용할 수 있다.
#include <iostream>
using namespace std;
class Animal { // 부모 클래스
public:
void makeSound() {
cout << "동물이 소리를 냅니다." << endl;
}
};
class Dog : public Animal { // Animal을 상속받는 자식 클래스
public:
void bark() {
cout << "멍멍!" << endl;
}
};
int main() {
Dog myDog;
myDog.makeSound(); // 부모 클래스의 메서드 사용 가능
myDog.bark(); // 자식 클래스의 고유 메서드
return 0;
}
위 코드에서 Animal을 상속받은 Dog는 Animal에서 정의되어 있는 makeSound()함수를 사용할 수 있다.
다형성
다형성이란 같은 이름의 메서드가 상황에 맞게 다르게 동작하는 것을 말하며 오버라이딩과 오버로딩을 통해 구현할 수 있다.
오버라이딩은 부모 클래스의 함수를 자식 클래스에서 재정의하여 다르게 동작하도록 설정하는 것을 의미한다.
#include <iostream>
using namespace std;
class Animal {
public:
virtual void makeSound() {
cout << "동물이 소리를 냅니다." << endl;
}
};
class Dog : public Animal {
public:
void makeSound() override { // 부모 클래스의 메서드를 재정의
cout << "멍멍!" << endl;
}
};
class Cat : public Animal {
public:
void makeSound() override {
cout << "야옹!" << endl;
}
};
int main() {
Animal* myAnimal1 = new Dog();
Animal* myAnimal2 = new Cat();
myAnimal1->makeSound(); // "멍멍!"
myAnimal2->makeSound(); // "야옹!"
delete myAnimal1;
delete myAnimal2;
return 0;
}
예를 들어 위 코드에서는 Dog와 Cat이 부모 클래스 Animal로부터 makeSound()를 오버라이딩하였다.
그리고 각각 "멍멍!", "야옹!"을 출력하도록 설정하여 makeSound()는 상황에 맞게 다르게 동작하도록 되었다.
오버로딩은 같은 이름의 함수이지만 매개변수의 개수나 타입에 따라 다르게 동작하도록 설정하는 것을 의미한다.
#include <iostream>
using namespace std;
class Plus{
public:
void plus(int a, int b){
cout << a + b << endl;
}
void plus(int a, int b, int c){
cout << a + b + c << endl;
}
};
int main(){
Plus p;
p.plus(1, 2); // 3
p.plus(1, 2, 3); // 6
}
위 코드에서는 plus() 함수를 오버로딩하여 a, b를 받는 경우와 a, b, c를 받는 경우로 정의하였다.
이에 따라 plus(1, 2)의 경우 1+2 = 3을, plus(1, 2, 3)의 경우 1+2+3 = 6을 출력하도록 되었다.
추상화
추상화란 객체들이 공통적으로 필요로 하는 속성이나 동작을 하나로 추출해 내는 작업이다.
예를 들어 자동차, 비행기가 있을 때 이들은 "이동한다"는 공통점을 가지고 있고 "탈 것"으로 추상화할 수 있다.
이를 통해 중요한 기능만 노출하고 세부 사항을 숨길 수 있다.
#include <iostream>
using namespace std;
class Vehicle {
public:
virtual void move() = 0; // 추상 메소드
};
class Car : public Vehicle {
public:
void move() override {
cout << "자동차가 도로 위를 달립니다." << endl;
}
};
class Airplane : public Vehicle {
public:
void move() override {
cout << "비행기가 하늘을 납니다." << endl;
}
};
int main() {
Vehicle* v1 = new Car();
Vehicle* v2 = new Airplane();
v1->move(); // "자동차가 도로 위를 달립니다."
v2->move(); // "비행기가 하늘을 납니다."
}
위 코드에서 Vehicle의 move()함수는 추상 메소드로 정의되어 자식 클래스인 Car와 Airplane에서 사용된다.