객체지향 프로그래밍의 4가지 특징
객체지향 프로그래밍에는 중요한 특징으로 추상화(Abstraction), 캡슐화(Encapsulation), 상속(Inheritance), 다형성(Polymorphism) 총 4가지가 있다.
추상화
추상화는 객체의 중요한 속성과 동작만을 모델링하고 불필요한 세부 사항은 숨기는 것을 의미한다.
즉, 복잡한 시스템을 단순화하여 핵심적인 개념만 표현하는 기법이다.
객체지향 프로그래밍에서는 클래스를 통해 객체의 핵심적인 속성과 동작을 정의함으로써 이러한 추상화를 구현할 수 있다.
예를 들어 자동차를 프로그램으로 표현할 때 내부 엔진 구조까지 구현하기보다는 다음과 같은 요소만 표현할 수 있다.
속도
연료
주행
정지
자동차를 표현하는 클래스가 있다고 가정했을 때, 엔진의 내부 구조나 복잡한 동작을 모두 구현하기보다는 속도, 연료, 주행과 같은 핵심적인 기능만을 모델링할 수 있다.
예시 코드
이전에 작성했던 간단한 텍스트 기반 게임 구조를 예시에 추가적으로 코드를 수정했다.
public abstract class Character
{
public string charName { get; private set; }
public int hp { get; private set; }
public int mp { get; private set; }
protected int damage { get; private set; }
public bool isDead { get; private set; }
public Character(string name, int charHp, int charMp, int dmg)
{
charName = name;
hp = charHp;
mp = charMp;
damage = dmg;
isDead = false;
}
// 캐릭터마다 공격 방식이 다르기 때문에 구현하지 않음
public abstract void Attack(Character target);
public void TakeDamage(int dmg)
{
hp -= dmg;
Console.WriteLine($"{charName}은 {dmg}의 피해를 입었습니다.");
if (hp <= 0)
{
hp = 0;
isDead = true;
Console.WriteLine($"{charName}은 사망했습니다.");
}
}
}
Character 클래스는 캐릭터가 공통적으로 가지는 속성과 기능을 정의한 추상 클래스이다.
공격 방식은 캐릭터마다 다를 수 있기 때문에 Attack() 메서드는 abstract로 선언하여 구현하지 않았다.
이렇게 공통적인 개념만 정의하고 구체적인 동작은 하위 클래스에서 구현하도록 하는 것이 객체지향 프로그래밍에서의 추상화이다.
캡슐화 (Encapsulation)
캡슐화는 데이터와 메서드를 하나의 객체로 묶고, 데이터에 대한 접근을 제한하여 객체의 내부 상태를 보호하는 기법이다.
이를 통해 데이터의 무결성을 유지하고 객체의 사용 방법을 단순화할 수 있다.
객체지향 프로그래밍에서는 보통 접근 제한자를 사용하여 클래스의 속성과 메서드에 대한 접근 범위를 설정한다.
예를 들어 다음과 같은 접근 제한자가 있다.
public
private
protected
객체는 자신의 데이터를 외부에서 직접 수정하지 못하도록 보호하고, 정의된 메서드를 통해서만 데이터에 접근하도록 설계한다.
이러한 방식을 통해 정보 은닉(Information Hiding)을 구현할 수 있으며, 객체의 내부 구현을 숨기고 외부에는 필요한 인터페이스만 제공함으로써 프로그램의 안정성과 유지보수성을 높일 수 있다.
public string charName { get; private set; }
public int hp { get; private set; }
public int mp { get; private set; }
protected int damage { get; private set; }
public bool isDead { get; private set; }
위에서 작성했던 예시 코드를 보면 Character 클래스 내부의 데이터가 캡슐화를 통해 외부에서 직접 접근할 수 없도록 설계되어 있다.
예를 들어 hp 속성은 private set을 사용하여 외부에서 직접 수정할 수 없도록 제한하였다. 따라서 체력 값은 TakeDamage()와 같은 메서드를 통해서만 변경될 수 있다.
이러한 방식은 객체의 내부 데이터를 보호하는 캡슐화의 대표적인 예라고 할 수 있다.
캡슐화 = 데이터 보호 + 접근 제어
private / protected / getter / setter = 구현 방법
상속(Inheritance)
상속은 기존 클래스의 속성과 메서드를 새로운 클래스가 물려받아 사용할 수 있도록 하는 기능이다.
이를 통해 코드의 재사용성을 높이고, 클래스 간 계층 구조를 형성하여 프로그램을 보다 체계적으로 설계할 수 있다.
기존 클래스를 부모 클래스(Parent Class) 또는 슈퍼 클래스(Super Class)라고 하며, 이를 상속받는 클래스를 자식 클래스(Child Class) 또는 서브 클래스(Sub Class)라고 한다.
자식 클래스는 부모 클래스의 속성과 메서드를 그대로 사용할 수 있으며, 필요에 따라 새로운 속성이나 메서드를 추가하여 기능을 확장할 수 있다.
상속을 사용하면 공통된 기능을 부모 클래스에 정의하고, 이를 여러 자식 클래스에서 공유할 수 있기 때문에 코드의 중복을 줄이고 유지보수성을 높일 수 있다.
예를 들어 다음과 같은 클래스 구조를 생각할 수 있다.
├ Warrior
├ Mage
└ Archer
여기서 Warrior, Mage, Archer 클래스는 Character 클래스를 상속받아 캐릭터가 공통적으로 가지는 속성과 메서드를 사용할 수 있다. 이처럼 상속을 활용하면 공통된 기능을 부모 클래스에 정의하고 각 자식 클래스에서는 자신에게 필요한 기능을 추가하여 프로그램을 효율적으로 설계할 수 있다.
예시 코드
부모 클래스
public abstract class Character
{
protected static Random random = new Random();
public string charName { get; private set; }
public int hp { get; private set; }
public int mp { get; private set; }
protected int damage { get; private set; }
public bool isDead { get; private set; }
public Character(string name, int charHp, int charMp, int dmg)
{
charName = name;
hp = charHp;
mp = charMp;
damage = dmg;
isDead = false;
}
public abstract void Attack(Character target);
public void TakeDamage(int dmg)
{
hp -= dmg;
Console.WriteLine($"{charName}은 {dmg}의 피해를 입었습니다.");
if (hp <= 0)
{
hp = 0;
isDead = true;
Console.WriteLine($"{charName}은 사망했습니다.");
}
}
}
자식클래스
public class Warrior : Character
{
public Warrior(string name, int charHp, int charMp, int dmg)
: base(name, charHp, charMp, dmg) { }
public override void Attack(Character target)
{
Console.WriteLine($"{charName}가 {target.charName}에게 검을 휘둘러 {damage}의 피해를 주었다.");
target.TakeDamage(damage);
}
}
public class Archor : Character
{
public Archor(string name, int charHp, int charMp, int dmg)
: base(name, charHp, charMp, dmg) { }
//궁수는 2발의 화살을 사용해 공격합니다. 대신 30% 확률로 화살 한발이 빗나갑니다.
public override void Attack(Character target)
{
int chance = random.Next(100);
if (chance < 30)
{
Console.WriteLine($"{charName}의 화살 한 발이 빗나갔습니다!");
target.TakeDamage(damage);
}
else
{
Console.WriteLine($"{charName}가 더블 어택을 사용했다!");
target.TakeDamage(damage);
target.TakeDamage(damage);
}
}
}
Warrior와 Archer 클래스는 Character 클래스를 상속받은 자식 클래스이다.
두 클래스 모두 Attack() 메서드를 override하여 각 캐릭터의 공격 방식을 다르게 구현하였다.
이처럼 동일한 메서드가 객체의 타입에 따라 서로 다른 방식으로 동작하는 것을 다형성이라고 한다.
다형성(Polymorphism)
다형성은 동일한 메서드가 상황이나 객체의 타입에 따라 서로 다른 방식으로 동작하는 것을 의미한다.
객체지향 프로그래밍에서는 같은 인터페이스(메서드 이름)를 사용하더라도 객체의 타입에 따라 다른 동작을 수행할 수 있다.
예를 들어 같은 Attack() 메서드라도 캐릭터의 종류에 따라 다른 방식으로 동작할 수 있다.
이처럼 같은 이름의 메서드가 객체에 따라 다른 형태로 동작하는 특징을 다형성이라고 한다.
다형성 구현 방법
C#에서는 다형성을 오버로딩(Overloading)과 오버라이딩(Overriding)을 통해 구현할 수 있다.
오버로딩 (Overloading)
오버로딩은 같은 이름의 메서드를 매개변수의 타입이나 개수에 따라 다르게 정의하는 것이다.
void Attack()
void Attack(int damage)
void Attack(string skillName)
메서드 이름은 같지만 매개변수가 다르기 때문에 서로 다른 메서드로 동작한다.
오버라이딩 (Overriding)
오버라이딩은 부모 클래스에서 정의된 메서드를 자식 클래스에서 재정의하는 것이다.
예를 들어 Character 클래스에 Attack() 메서드가 정의되어 있고
이를 Warrior, Mage, Archer 클래스에서 각각 다르게 구현할 수 있다.
public override void Attack(Character target)
{
Console.WriteLine($"{charName}가 {target.charName}에게 검을 휘둘러 {damage}의 피해를 주었다.");
target.TakeDamage(damage);
}
다형성은 오버로딩과 오버라이딩을 통해 구현될 수 있으며, 특히 상속 구조에서 메서드를 재정의하는 오버라이딩이 객체지향 프로그래밍에서 많이 사용된다.
예시 코드
예시 코드에서는 게임 시작 후 캐릭터를 선택하고 몬스터와 전투를 진행하는 간단한 텍스트 기반 상호작용을 구현하였다. 이를 통해 객체 간의 상호작용과 다형성의 동작을 확인할 수 있다.
static void Main(string[] args)
{
Console.WriteLine("게임 시작하기 : 1");
Console.WriteLine("게임 종료하기 : 2");
string input = Console.ReadLine();
if (input != "1")
{
Environment.Exit(0);
}
Console.Clear();
Console.WriteLine("캐릭터를 생성하세요");
Console.WriteLine("1 : 전사");
Console.WriteLine("2 : 궁수");
input = Console.ReadLine();
Character player = null;
Character monster = new Warrior("Golem", 150, 0, 10);
if (input == "1")
{
player = new Warrior("Adel", 100, 50, 20);
}
else if (input == "2")
{
player = new Archer("Lena", 80, 35, 15);
}
else
{
Console.WriteLine("잘못된 입력입니다.");
return;
}
Console.Clear();
Console.WriteLine("전투 시작!");
while (true)
{
Console.WriteLine();
Console.WriteLine("1. 공격");
Console.WriteLine("2. 종료");
input = Console.ReadLine();
if (input == "1")
{
Console.Clear();
player.Attack(monster);
if (monster.isDead)
{
Console.WriteLine("몬스터를 처치했습니다!");
break;
}
Console.WriteLine("\n몬스터의 턴!");
monster.Attack(player);
if (player.isDead)
{
Console.WriteLine("플레이어가 사망했습니다.");
break;
}
}
else if (input == "2")
{
Console.WriteLine("게임을 종료합니다.");
break;
}
else
{
Console.WriteLine("잘못된 입력입니다.");
}
}
Console.WriteLine("\n게임 종료");
Console.ReadKey();
}
위 코드에서 player 변수의 타입은 Character이지만, 실제로는 Warrior 또는 Archer 객체가 생성된다.
이후 player.Attack(monster)를 호출하면 같은 Attack() 메서드를 사용하더라도 객체의 실제 타입에 따라 서로 다른 방식으로 동작한다.
예를 들어 Warrior는 검을 이용한 근접 공격을 수행하고, Archer는 확률에 따라 두 번 공격하거나 한 발이 빗나가는 공격을 수행한다.
이처럼 같은 메서드 호출이 객체의 실제 타입에 따라 서로 다른 동작을 수행하는 특징을 객체지향 프로그래밍에서는 다형성(Polymorphism)이라고 한다. 다형성을 통해 부모 타입의 참조로 다양한 자식 객체를 다룰 수 있으며, 동일한 인터페이스를 사용하면서도 객체마다 다른 동작을 수행하도록 만들 수 있다.
전체 코드 실행 결과 이미지






'CS > 객체지향 프로그래밍' 카테고리의 다른 글
| 객체지향 프로그래밍(OOP)의 장단점 (0) | 2026.04.08 |
|---|---|
| 객체지향 프로그래밍(Object-Oriented Programming)이란? (0) | 2026.04.07 |