OOP란?
OOP는 소프트웨어 설계 패러다임 중 하나로, 데이터를 객체(Object)라는 단위로 묶고,
이 객체들이 데이터를 처리하는 방식을 정의한다.
주요 특징들은 다음과 같다.
I. 캡슐화(Encapsulation)
캡슐화는 데이터와 메서드를 객체 안에 숨겨 외부 접근을 제한하고, 데이터를 보호하는 특징을 가진다.
외부에서는 클래스에서 제공하는 공용(public) 인터페이스에만 접근할 수 있다.
이 방식은 다음과 같은 장점을 지닌다.
- 데이터 무결성 : 잘못된 접근이다 수정으로부터 데이터를 보호한다.
- 코드 변경 용이성: 내부 구현을 변경하더라도 외부에 영향을 최소화할 수 있다.
다음은 캡슐화의 예제이다.
#include <iostream>
using namespace std;
class BankAccount {
private:
int balance; // 외부에서 접근 불가능
public:
BankAccount() : balance(0) {}
void deposit(int amount) {
if (amount > 0) {
balance += amount;
}
}
void withdraw(int amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
}
}
int getBalance() const {
return balance; // 안전하게 balance에 접근
}
};
int main() {
BankAccount account;
account.deposit(1000);
account.withdraw(500);
cout << "Balance: " << account.getBalance() << endl; // Balance: 500
return 0;
}
- 데이터 보호 및 무결성 유지
balance와 같은 중요한 데이터는 private으로 선언되어 외부에서 직접 접근할 수 없다.
또한, deposit 및 withdraw 메소드는 데이터의 변경을 제한하며, 잘못된 값(예: 음수)이 입력되지 않도록
유효성 검사를 수행한다.
이로써 데이터 보호 및 무결성을 유지한다.
- 코드의 유지보수성 향상
getBalance 메소드를 통해 balance의 값에 접근하도록 강제하면, 내부 데이터 구조를 변경하더라도
외부에서 사용하는 인터페이스는 그대로 유지된다.
클래스 외부 코드가 balance와 직접 상호작용하지 않으므로, 변경으로 인한 오류 발생 가능성이 줄어든다.
II. 상속(Inheritance)
상속은 기존 클래스의 속성과 메소드를 재사용하거나 확장하기 위한 메커니즘이다.
이를 통해 코드 재사용성을 높이고 계층적 관계를 구성할 수 있다.
상위 클래스는 일반적인 기능을, 하위 클래스는 구체적인 기능을 구현한다.
상속은 다음과 같은 장점을 제공한다.
- 코드 재사용 : 공통 코드를 상위 클래스에 정의, 하위 클래스에서 이를 공유한다.
- 유지보수성 : 코드를 한 번 수정하면 관련된 모든 하위 클래스에 적용된다.
다음은 상속의 예제이다.
#include <iostream>
using namespace std;
// 상위 클래스 (Base Class)
class Vehicle {
protected:
int speed;
public:
Vehicle() : speed(0) {}
void setSpeed(int s) {
speed = s;
}
void showSpeed() const {
cout << "Speed: " << speed << " km/h" << endl;
}
};
// 하위 클래스 (Derived Class) - Car
class Car : public Vehicle {
private:
int fuel; // 연료량
public:
Car() : fuel(0) {}
void refuel(int amount) {
fuel += amount;
cout << "Car refueled with " << amount << " liters. Fuel now: " << fuel << " liters." << endl;
}
};
// 하위 클래스 (Derived Class) - Bicycle
class Bicycle : public Vehicle {
public:
void ringBell() const {
cout << "Bicycle bell rings: Ding Ding!" << endl;
}
};
int main() {
Car myCar;
Bicycle myBike;
// Car 객체 사용
myCar.setSpeed(100);
myCar.showSpeed();
myCar.refuel(50);
// Bicycle 객체 사용
myBike.setSpeed(20);
myBike.showSpeed();
myBike.ringBell();
return 0;
}
- 코드 재사용성
Vehicle 클래스의 speed와 관련된 기능은 모든 이동 수단에서 공통적으로 사용되므로,
이를 재사용하여 중복된 코드를 줄일 수 있다.
- 유지보수성
상위 클래스 (Vehicle)에서 공통 기능을 수정하면 이를 상속받은 모든 하위 클래스 (Car. Bicycle) 에도
수정 사항이 자동으로 반영된다.
이를 통해 유지보수가 편리해진다.
III. 다형성(Polymorphism)
다형성은 동일한 인터페이스가 여러 형태로 동작할 수 있도록 하는 기능이다.
이를 통해 동일한 메서드를 호출하더라도 객체의 실제 타입에 따라 서로 다른 동작을 수행할 수 있다.
C++에서는 주로 가상 함수(Virtual Function)를 통해 구현한다.
다형성은 다음과 같은 유용성을 제공한다.
- 인터페이스 일관성 : 다형성을 사용하면 서로 다른 객체를 동일한 방식으로 다룰 수 있다.
- 확장성 : 새로운 객체 타입이 추가되더라도 기존 코드를 최소한으로 수정한다.
다음은 다형성의 예제이다.
#include <iostream>
using namespace std;
// 상위 클래스 (Base Class)
class Shape {
public:
// 가상 함수 (Virtual Function) - 다형성을 구현
virtual void draw() const {
cout << "Drawing a shape..." << endl;
}
virtual ~Shape() {} // 가상 소멸자
};
// 하위 클래스 (Derived Class) - Circle
class Circle : public Shape {
public:
void draw() const override { // Override로 동작 변경
cout << "Drawing a circle..." << endl;
}
};
// 하위 클래스 (Derived Class) - Rectangle
class Rectangle : public Shape {
public:
void draw() const override {
cout << "Drawing a rectangle..." << endl;
}
};
int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Rectangle();
shape1->draw(); // Circle 클래스의 draw 메서드 호출
shape2->draw(); // Rectangle 클래스의 draw 메서드 호출
delete shape1; // 메모리 해제
delete shape2; // 메모리 해제
return 0;
}
- 동일한 인터페이스를 작업
Shape 포인터를 사용하여 모든 하위 클래스(Circle, Retangle)와 동일한 인터페이스로 작업할 수 있다.
- 확장성
새로 추가된 도형 (Ex. Triangle) 클래스에서 draw() 메서드를 구현하면 기존 코드 수정 없이 추가할 수 있다.
IV. 추상화 (Abstraction)
추상화는 불필요한 세부 사항을 숨기고, 객체의 핵심적인 특징만 노출하는 기법이다.
추상 클래스와 인터페이스를 통해 구현되며, 주로 공통 동작의 규격을 정의한다.
추상화는 사용자에게 더 높은 수준의 구조를 제공하여 복잡성을 줄이고, 시스템 설계를 간결하게 만든다.
추상화는 다음과 같은 장점을 제공한다.
- 중요한 요소에만 집중할 수 있도록 하고, 불필요한 세부 사항을 감춰 시스템을 설계하거나 이해할 때
단순하고 명확하게 접근 가능
- 새로운 기능이나 객체 타입을 추가할 때 기존 코드를 거의 수정하지 않아도 된다.
다음은 추상화의 예제이다.
#include <iostream>
using namespace std;
// 추상 클래스 (Abstract Class)
class Animal {
public:
virtual void sound() const = 0; // 순수 가상 함수 (Abstract Method)
virtual ~Animal() {} // 가상 소멸자
};
// Dog 클래스 - Animal을 상속받아 구현
class Dog : public Animal {
public:
void sound() const override {
cout << "Woof Woof!" << endl;
}
};
// Cat 클래스 - Animal을 상속받아 구현
class Cat : public Animal {
public:
void sound() const override {
cout << "Meow Meow!" << endl;
}
};
int main() {
Animal* dog = new Dog();
Animal* cat = new Cat();
dog->sound(); // Dog 클래스의 sound 메서드 호출
cat->sound(); // Cat 클래스의 sound 메서드 호출
delete dog; // 메모리 해제
delete cat; // 메모리 해제
return 0;
}
- 공통 인터페이스 제공
Animal 클래스는 모든 동물이 반드시 구현해야 하는 sound() 메서드를 규정하며, 일관성을 제공한다.
- 불필요한 세부 사항 숨김
사용자는 sound() 메서드가 동작하는 방식에 대해 알 필요 없이, 각 동물의 소리를 호출할 수 있다.
- 확장성
새로운 동물 클래스를 추가할 때, Animal의 인터페이스를 구현하여 간단히 추가할 수 있다.
게임 프로그래밍에서 사용될 수 있는 OOP
앞서 말한 OOP의 특징들은 게임 프로그래밍에서도 중요한 역할을 하는 것으로 보인다.
OOP는 게임 개발 과정에서 코드를 더 효율적이고 관리 가능하며 확장을 용이하게 만들 수 있다.
게임 프로그래밍에서의 OOP가 어떻게 활용되는지 설명하고 각 특징이 어떻게 적용되는지 살펴보려 한다.
Ex. (Unity)
간단한 Player와 Enemy 시스템을 OOP를 활용해 구현해보았다.
Player.cs
using UnityEngine;
// 상위 클래스 (Base Class)
public class Character : MonoBehaviour
{
public string characterName;
public int health;
public virtual void TakeDamage(int damage)
{
health -= damage;
Debug.Log(characterName + " takes " + damage + " damage. Health: " + health);
}
}
// 하위 클래스 (Derived Class) - Player
public class Player : Character
{
public int experience;
public void GainExperience(int xp)
{
experience += xp;
Debug.Log(characterName + " gained " + xp + " experience. Total XP: " + experience);
}
public override void TakeDamage(int damage)
{
health -= damage;
Debug.Log("Player " + characterName + " takes " + damage + " damage! Health: " + health);
}
}
// 하위 클래스 (Derived Class) - Enemy
public class Enemy : Character
{
public override void TakeDamage(int damage)
{
health -= damage;
Debug.Log("Enemy " + characterName + " takes " + damage + " damage! Health: " + health);
}
}
GameManager.cs
using UnityEngine;
public class GameManager : MonoBehaviour
{
void Start()
{
// Player 객체 생성
Player player = new Player();
player.characterName = "Hero";
player.health = 100;
player.experience = 0;
// Enemy 객체 생성
Enemy enemy = new Enemy();
enemy.characterName = "Goblin";
enemy.health = 50;
// 상호작용 예시
enemy.TakeDamage(10); // 고블린이 10 피해를 입었습니다. Health: 40
player.TakeDamage(20); // 플레이어가 20 피해를 입었습니다. Health: 80
player.GainExperience(50); // 플레이어가 50 경험치를 획득했습니다. Total XP: 50
}
}
Unity에서의 OOP 특징은 다음과 같다.
I. 캡슐화
Character 클래스에서 health 속성화 TakeDamage 메서드는 객체 내부에서 관리된다.
외부에서는 객체의 상태를 메서드를 통해 접근하므로 무결성을 유지할 수 있다.
II. 상속
Player와 Enemy 클래스는 Character 클래스를 상속받아 공통 속성과 메서드를 재사용하며,
자신만의 고유 기능을 추가한다.
III. 다형성
TakeDamage 메서드는 virtual로 정의되어, Player와 Enemy 클래스에서 각기 다른 방식으로 구현되었다.
게임 로직에서 객체의 타입에 따라 다른 동작을 수행한다.
IV. 추상화
Character 클래스는 공통적인 기능을 정의하며, 이를 상속하는 클래스들이 세부적인 구현을 제공,
복잡성을 줄이고 핵심 구조를 명확히 한다.
'Computer Science' 카테고리의 다른 글
C언어의 모든 것(C, C++, C#) (0) | 2023.10.20 |
---|---|
OSI 7계층 (0) | 2023.10.20 |