객체지향 프로그래밍(OOP)이란?
객체지향 프로그래밍(OOP, Object-Oriented Programming)은 소프트웨어 개발 패러다임 중 하나로, 현실 세계의 개념을 컴퓨터 프로그램으로 모델링하는 방법론을 의미한다.
프로그램을 객체(Object)라는 기본 단위의 집합으로 구성하고, 이 객체들 간의 상호작용을 통해 프로그램을 설계하고 구현한다.
✒ 객체(Object)란?
객체(Object)는 데이터와 그 데이터를 처리하는 함수를 함께 묶은 독립적인 모듈이라 볼 수 있다.
(아래에서 더 자세히 알아보자!)
객체지향 프로그래밍과 절차지향 프로그래밍
객체지향 프로그래밍(OOP)은 복잡한 문제를 보다 구조화된 방식으로 해결할 수 있게 도와주며, 대규모 소프트웨어 개발에서는 매우 중요한 개념이라고 볼 수 있다.
❓ 그러면 객체지향 프로그래밍의 이전 패러다임은 무엇이 있었을까? 객체지향 프로그래밍 이전에는 어떤 식으로 프로그램을 설계하고 구현했을까?
객체지향 프로그래밍(OOP) 이전의 주요 프로그래밍 패러다임은 바로, 절차지향 프로그래밍(Procedural Programming)이다.
절차지향 프로그래밍(Procedural Programming)은 프로그램을 일련의 절차나 함수로 구성하는 방법론이다. 이 방법론으로 구현된 프로그램은 시작부터 끝까지 순차적으로 실행되는 일련의 명령어로 구성되어 있다고 보면 된다.
따라서, 절차지향 프로그래밍은 간단하고 직관적인 문제 해결에 적합하며, 객체지향 프로그래밍은 복잡한 시스템을 더 잘 모델링하고 유지보수할 수 있도록 도와준다.
참고 | 객체지향과 절차지향의 차이
객체지향과 절차지향의 개념과 특징을 보고 더 자세한 차이점을 알고 싶다면 참고하자!
[CS] 객체지향 프로그래밍과 절차지향 프로그래밍의 차이
객체지향 프로그래밍(OOP)의 핵심 개념 | 객체(Object)와 클래스(Class)
- 객체 (Object)
- 객체는 데이터와 이 데이터를 처리하는 연산(함수)을 함께 묶어서 하나의 단위로 취급한다.
- 클래스에서 정의한 구조를 실제로 구현한 실체로, 클래스의 인스턴스(instance)이다.
- 각 객체는 클래스에서 정의된 속성(데이터)과 메서드(함수)를 가진다.
- 클래스 (Class)
- 객체를 생성하기 위한 설계도이다.
- 속성(데이터)과 메서드(함수)를 정의한다.
결국 객체는 프로그램의 기본 단위가 되고, 이러한 객체를 생성하고 관리하는 설계도가 클래스이다.
예시 코드 | 객체, 클래스
게임 캐릭터와 무기를 객체로 모델링하여 상호작용하는 것을 C#으로 간단하게 구현해 봤다.
- 클래스: Character와 Weapon
- 속성: 캐릭터의 이름, 체력, 무기. 무기의 이름과 공격력.
- 메서드: 캐릭터가 공격하고, 체력을 감소시키는 메서드.
무기와 캐릭터 설계도(클래스)를 작성하고,
각각의 설계도(클래스)를 이용하여 무기는 Sword, Axe 객체를 생성하고, 캐릭터는 히어로(hero)와 악당(villain)을 생성하는 코드이다.
using System;
//-- Weapon 클래스 정의
public class Weapon
{
public string Name { get; private set; } // 무기의 이름
public int Damage { get; private set; } // 무기의 공격력
//. 초기화
public Weapon(string name, int damage)
{
Name = name;
Damage = damage;
}
}
//-- Character 클래스 정의
public class Character
{
public string Name { get; private set; } // 캐릭터의 이름
public int Health { get; private set; } // 캐릭터의 체력
public Weapon Weapon { get; private set; } // 캐릭터가 장착한 무기
//. 초기화
public Character(string name, int health)
{
Name = name;
Health = health;
Weapon = null;
}
//. 무기 장착 메서드
public void EquipWeapon(Weapon weapon)
{
Weapon = weapon;
Console.WriteLine("🏹 무기 장착!");
}
//. 공격 메서드
public void Attack(Character other)
{
Console.WriteLine("💣 공격!");
}
//. 체력 감소 메서드 (데미지 입었을 때)
public void TakeDamage(int damage)
{
Health -= damage;
Console.WriteLine("💔 체력 감소");
}
}
class Program
{
static void Main()
{
// 캐릭터 생성
Character hero = new Character("히어로", 100);
Character villain = new Character("악당", 80);
// 무기 생성
Weapon sword = new Weapon("Sword", 15);
Weapon axe = new Weapon("Axe", 20);
// 무기 장착
hero.EquipWeapon(sword);
villain.EquipWeapon(axe);
// 공격
hero.Attack(villain);
villain.Attack(hero);
}
}
객체지향 프로그래밍(OOP)의 특징 4가지
객체지향 프로그래밍에는 중요한 특징인 추상화(Abstraction), 캡슐화(Encapsulation), 상속(Inheritance), 다형성(Polymorphism) 총 4가지가 있다.
- 추상화는 중요한 부분만 모델링하고 세부 사항을 숨김
- 캡슐화는 데이터와 메서드를 하나로 묶고 데이터 접근을 제한함
- 상속은 기존 클래스를 기반으로 새로운 클래스를 만드는 기능
- 다형성은 동일한 메서드가 여러 형태로 동작할 수 있는 능력
아래에서 더 자세히 알아보자.
1. 추상화 (Abstraction)
복잡성을 줄이고, 시스템을 더 쉽게 이해하고 다룰 수 있음
- 복잡한 시스템을 단순화하여 중요한 부분만 표현하는 기법
- 객체지향 프로그래밍(OOP)에서는 클래스를 사용하여 객체의 핵심적인 속성(데이터)과 동작(함수)만을 정의하고, 세부 사항은 감춤
예제 코드
아래의 GameCharacter는 추상 클래스이다.
이 클래스는 공통된 속성(Name과 Health)을 가지고 있으며, Attack 메서드는 추상 메서드로 정의되어, 파생 클래스에서 구현하도록 되어있다.
public abstract class GameCharacter
{
public string Name { get; private set; }
public int Health { get; private set; }
public GameCharacter(string name, int health)
{
Name = name;
Health = health;
}
public abstract void Attack(GameCharacter target);
}
2. 캡슐화 (Encapsulation)
데이터 무결성을 유지하고, 객체의 사용 방법을 단순화함
- 데이터와 메서드를 하나로 묶고, 데이터의 접근을 제한하여 객체의 내부 상태를 보호하는 기법
- 보통 접근 제한자(예: public, private, protected)를 사용하여 클래스의 속성과 메서드의 접근 범위를 설정
- 객체는 자신의 데이터를 외부에서 직접 접근하지 못하도록 보호하고, 오직 정의된 메서드를 통해서만 접근하도록 함
- 이는 정보 은닉(Information Hiding)을 통해 객체의 내부 구현을 숨기고 외부 인터페이스만을 제공함으로써 객체의 안정성과 유지보수성을 증가
예제 코드
아래의 Player 클래스는 health 필드를 캡슐화하고, Health 속성을 통해 간접적으로 접근할 수 있도록 한다. Health 속성은 설정자(setter)에서 값 검사를 수행하여 무결성을 유지한다.
public class Player
{
private int health;
public string Name { get; private set; }
public int Health
{
get { return health; }
private set
{
if (value < 0) // 값 검사
health = 0;
else
health = value;
}
}
public Player(string name, int health)
{
Name = name;
Health = health;
}
public void TakeDamage(int damage)
{
Health -= damage;
Console.WriteLine($"🏹 플레이어 {Name}가 {damage}만큼의 데미지를 입었다!. ❤ 남은체력:{Health}");
}
}
3. 상속 (Inheritance)
코드의 재사용성을 높이고, 클래스 간의 계층 구조를 형성하여 코드의 구조화와 관리를 용이하게 함
- 기존 클래스(부모 클래스 또는 슈퍼 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스 또는 서브 클래스)가 물려받는 기능
- 자식 클래스는 부모 클래스의 속성과 메서드를 상속받아 사용할 수 있으며, 추가적인 속성이나 메서드를 정의할 수 있음
- 상속을 사용하면 기존 클래스의 기능을 확장하거나 수정할 수 있으며, 공통된 기능을 부모 클래스에 정의하고 이를 여러 자식 클래스에서 공유할 수 있음
예제 코드
아래의 Warrior 클래스는 GameCharacter 클래스를 상속받아 Attack 메서드를 구현한다. 상속을 통해 GameCharacter의 속성과 메서드를 물려받는다.
❕ 부모클래스인 GameCharacter는 위로 올라가 추상화에서 다룬 예제 코드를 참고하자
public class Warrior : GameCharacter
{
public Warrior(string name, int health) : base(name, health) { }
public override void Attack(GameCharacter target)
{
Console.WriteLine($"🏹 플레이어 {Name}가 {target.Name}에게 칼을 휘둘렀다!");
target.TakeDamage(10);
}
public void TakeDamage(int damage)
{
Health -= damage;
Console.WriteLine($"🏹 플레이어 {Name}가 {damage}만큼의 데미지를 입었다!. ❤ 남은체력:{Health}");
}
}
4. 다형성 (Polymorphism)
같은 이름의 메서드가 다양한 객체에서 다르게 구현될 수 있음
- 동일한 메서드가 여러 형태로 동작할 수 있게 하는 능력
- 같은 인터페이스를 통해 다양한 구현을 제공하며, 객체의 타입에 따라 다른 메서드가 호출
- 다형성은 오버로딩(Overloading)과 오버라이딩(Overriding)을 통해 구현
✒ 오버로딩(Overloading)과 오버라이딩(Overriding)
- 오버로딩 (Overloading)
- 같은 이름의 메서드를 매개변수의 타입이나 개수에 따라 다르게 구현하는 것.
- 오버라이딩 (Overriding)
- 부모 클래스에서 정의된 메서드를 자식 클래스에서 재정의하는 것.
예제 코드
바로 위 예제 코드에서 GameCharacter를 상속받는 자식 클래스 Warrior를 구현했던 것과 동일한 방법으로 Mage 클래스도 만들어보자.
Warrior와 Mage 클래스는 GameCharacter를 상속받아 Attack 메서드를 각각 다르게 구현한다.
다형성을 통해 GameCharacter 타입의 객체를 사용하여 다양한 Attack 동작을 수행할 수 있다.
❕ 부모클래스인 GameCharacter는 위로 올라가 추상화에서 다룬 예제 코드를 참고
❕ GameCharacter의 다른 자식 클래스인 Warrior는 위로 올라가 상속에서 다룬 예제 코드를 참고
public class Mage : GameCharacter
{
public Mage(string name, int health) : base(name, health) { }
public override void Attack(GameCharacter target)
{
Console.WriteLine($"🏹 플레이어 {Name}가 {target.Name}에게 마법공격을 시전했다!");
target.TakeDamage(15);
}
public void TakeDamage(int damage)
{
Health -= damage;
Console.WriteLine($"🏹 플레이어 {Name}가 {damage}만큼의 데미지를 입었다!. ❤ 남은체력:{Health}");
}
}
class Program
{
static void Main()
{
GameCharacter warrior = new Warrior("가렌", 100);
GameCharacter mage = new Mage("럭스", 80);
warrior.Attack(mage);
mage.Attack(warrior);
}
}
객체지향 프로그래밍(OOP)의 장점
객체지향 프로그래밍의 4가지 특징을 보면 장점이 잘 드러나는 걸 볼 수 있다.
아래처럼 정리해 보았다.
- 모듈화와 재사용성
- 클래스와 객체 단위로 모듈화 하여 개발할 수 있어 코드의 재사용성이 높아진다.
- 유지보수성
- 캡슐화와 정보 은닉을 통해 객체의 내부 구현을 숨기므로, 객체의 내부 변경이 외부 코드에 미치는 영향을 줄일 수 있다.
- 유연성과 확장성
- 상속과 다형성을 통해 기존 클래스에 새로운 기능을 쉽게 추가하고, 기존 코드를 확장할 수 있다.
- 코드의 가독성
- 현실 세계의 개념을 코드에 반영하여 이해하기 쉽고 유지보수가 용이한 코드를 작성할 수 있다.
이러한 특징들은 객체지향 프로그래밍이 복잡한 문제를 더 쉽게 다룰 수 있도록 돕는 이유이다.
OOP는 소프트웨어 설계의 중요한 패러다임으로 널리 사용되고 있으므로 잘 알아두면 좋을 듯하다.
'💾 Computer Science > Software Engineering' 카테고리의 다른 글
[CS/Design Pattern] GoF 디자인 패턴 정리 (0) | 2024.07.09 |
---|---|
[CS/OOP] 객체지향 프로그래밍과 절차지향 프로그래밍의 차이 (0) | 2024.07.08 |
[CS/OOP] 객체지향 설계의 5가지 기본 원칙 SOLID (0) | 2024.07.08 |