객체지향 프로그래밍을 처음 배우면 잘 와닿지도 않을 뿐더러 어떻게 코드를 작성 해야 하는지도 감이 잘 안 잡히실 겁니다. 이 문제점을 잘 알기에 이번 시간에는 객체지향 프로그래밍이 무엇인지 알아보고 그 장점과 단점을 소개하겠습니다. 마지막으로 실제 어떻게 사용되는지 예시코드를 제공하겠습니다.


[객체지향 프로그래밍이란?]

객체지향 프로그래밍은 코드를 설계하고 구현하는 방법 중 하나입니다. 이 뜻을 쉽게 풀어서 설명하겠습니다.

객체지향 프로그래밍은 현실 세계의 사물이나 개념을 프로그램 안에서 모델링하는 방식입니다. 여기서 사물이나 개념을 “객체”라고 합니다. 예를 들어, 게임에서 캐릭터, 총, 몬스터 등은 모두 객체가 될 수 있습니다. 이 객체들은 각각의 특징과 기능을 가지고 있습니다. 캐릭터는 이동하고 공격할 수 있고, 총은 발사할 수 있으며, 몬스터는 공격하고 피할 수 있습니다.

객체지향 프로그래밍에서 가장 중요한 개념 중 하나는 “클래스“입니다. 클래스는 객체의 틀이라고 생각할 수 있습니다. 클래스는 객체가 가져야 할 속성(특징)과 메서드(기능)를 정의합니다. 즉, 캐릭터 클래스는 캐릭터가 가져야 할 정보(체력, 공격력 등)와 할 수 있는 행동(이동, 공격 등)을 정의합니다.

그리고 객체 간의 상호작용도 객체지향 프로그래밍에서 중요합니다. 객체들은 서로 메시지를 주고받아 협력하며 작업을 수행합니다. 예를 들어, 캐릭터가 총을 발사하면 총 객체에게 발사 메시지를 보내어 총이 총알을 발사하도록 할 수 있습니다.

// 캐릭터 클래스 정의
public class Character
{
    // 속성(특징) 정의
    public int health;
    public int attackDamage;

    // 이동 메서드 정의
    public void Move()
    {
        Debug.Log("캐릭터가 이동합니다.");
    }

    // 공격 메서드 정의
    public void Attack()
    {
        Debug.Log("캐릭터가 공격합니다.");
    }
}

// 총 클래스 정의
public class Gun
{
    // 총알 발사 메서드 정의
    public void Shoot()
    {
        Debug.Log("총이 총알을 발사합니다.");
    }
}

// 몬스터 클래스 정의
public class Monster
{
    // 몬스터가 공격하는 메서드 정의
    public void Attack()
    {
        Debug.Log("몬스터가 공격합니다.");
    }

    // 몬스터가 피하는 메서드 정의
    public void Dodge()
    {
        Debug.Log("몬스터가 피합니다.");
    }
}

// 게임 실행 스크립트
public class Game : MonoBehaviour
{
    void Start()
    {
        // 객체 생성
        Character player = new Character();
        Gun playerGun = new Gun();
        Monster enemy = new Monster();

        // 객체의 속성 설정
        player.health = 100;
        player.attackDamage = 10;

        // 객체 간 상호작용
        player.Move(); // 캐릭터 이동
        player.Attack(); // 캐릭터 공격
        playerGun.Shoot(); // 총 발사
        enemy.Attack(); // 몬스터 공격
        enemy.Dodge(); // 몬스터 피함
    }
}

위 코드의 예시와 같이 객체들과 그 객체들이 할 수 있는 행동을 정의함으로 서로가 상호작용하는 것을 “객체지향 프로그래밍”이라 하는 것 입니다.


[객체지향 프로그래밍 장점]

  1. 코드의 구조화와 관리 용이성: 객체지향 프로그래밍은 코드를 객체 단위로 분할하고 각 객체가 독립적으로 존재하도록 구성합니다. 이는 코드의 구조화를 도와주며, 코드를 논리적으로 나누어 관리할 수 있게 합니다. 따라서 유지보수가 쉬워지고 버그를 찾기도 편리해집니다.
  2. 코드의 재사용성: 객체지향 프로그래밍에서는 클래스를 템플릿으로 사용하여 객체를 생성합니다. 이는 유사한 기능을 가진 객체를 간단히 생성할 수 있게 해줍니다. 예를 들어, 여러 종류의 몬스터가 있을 때 몬스터 클래스를 한 번 정의하고, 이를 상속하여 각각의 몬스터 객체를 생성할 수 있습니다.
  3. 유연성과 확장성: 객체지향 프로그래밍은 객체 간의 상호작용을 통해 코드를 작성합니다. 이는 기능을 확장하거나 변경할 때 기존 코드의 수정을 최소화할 수 있게 합니다. 예를 들어, 새로운 캐릭터나 무기를 추가하거나 기존의 기능을 수정할 때, 해당 객체의 클래스만 수정하면 되므로 다른 부분에 영향을 미치지 않습니다.
  4. 모듈화와 협업 용이성: 객체지향 프로그래밍은 각 객체가 독립적으로 존재하고 서로 협력하여 작업을 수행합니다. 이는 여러 프로그래머가 동시에 작업할 때 코드의 충돌을 최소화하고 모듈화된 형태로 작업할 수 있게 해줍니다. 따라서 대규모 프로젝트의 개발이 용이해집니다.
  5. 현실과의 유사성: 게임은 현실 세계의 요소들을 모방하여 만들어집니다. 객체지향 프로그래밍은 이러한 현실 세계의 모델링을 지원하므로 게임 개발에 적합합니다. 예를 들어, 캐릭터, 아이템, 몬스터 등의 게임 요소들을 객체로 표현하여 게임을 개발할 수 있습니다.

[객체지향 프로그래밍 단점]

  1. 복잡성과 학습 곡선: 객체지향 프로그래밍은 처음에는 이해하기 어려울 수 있습니다. 클래스, 객체, 상속, 다형성 등의 개념을 이해하고 활용하기 위해서는 시간과 노력이 필요합니다.
  2. 상속의 함정: 객체지향 프로그래밍에서는 상속을 사용하여 코드를 재사용할 수 있습니다. 하지만 상속을 남용하면 클래스 간의 강한 결합이 발생할 수 있고, 클래스 계층 구조가 복잡해질 수 있습니다. 이는 유지보수가 어려워지고 코드의 유연성이 감소할 수 있습니다.
  3. 높은 추상화 수준: 객체지향 프로그래밍은 현실 세계의 개념을 모델링하기 위해 높은 수준의 추상화를 사용합니다. 이로 인해 코드가 추상적이고 복잡해질 수 있습니다. 게임 개발에서는 종종 구체적인 요구사항에 맞춰 더 구체적인 접근이 필요할 수 있습니다.

[추상화란?]

추상화는 복잡한 시스템이나 개념을 단순화하여 중요한 부분에 집중할 수 있도록 하는 프로그래밍 개념입니다. 이는 일종의 개념화 과정으로, 구체적인 세부 사항을 숨기고 핵심적인 기능이나 특징에만 집중함으로써 코드를 더 간결하고 이해하기 쉽게 만듭니다.

은행 시스템을 만든다고 할때, 은행 시스템에는 고객, 계좌, 거래 등 많은 요소가 있을 겁니다. 이때 추상화를 이용하면 다음과 같이 생각할 수 있습니다:

  1. 고객(Customer): 개별 고객의 개인 정보나 계좌 상태는 신경쓰지 않고, 고객을 식별할 수 있는 고유한 ID나 이름만을 고려합니다. 예를 들어, 고객은 이름과 고유한 ID를 가지고 있으며, 계좌를 개설할 수 있습니다.
  2. 계좌(Account): 각 계좌는 고유한 계좌 번호와 잔액 정보를 갖고 있습니다. 실제로는 예금, 적금, 대출 등 다양한 종류의 계좌가 있을 수 있지만, 추상화 단계에서는 이러한 세부 사항은 고려하지 않고 단순히 계좌 번호와 잔액만을 고려합니다.
  3. 거래(Transaction): 고객이 입금, 출금, 이체 등의 거래를 수행할 때는 해당 거래의 유형과 금액만을 고려합니다. 실제로는 거래 시 발생하는 다양한 이벤트나 보안 등의 사항이 있겠지만, 추상화 단계에서는 이러한 세부 사항을 무시하고 거래의 유형과 금액만을 고려합니다.

[추상화를 하면 좋은점이 무엇일까?]

처음 개발을 시작할 때 의문이였던 것이 추상화를 하면 좋다는 건 알겠는데 어떻게 왜? 좋은지 확실이 감이 안왔었습니다. 저 같이 의문을 품었던 분들을 위해서 정확하게 설명 드리겠습니다.

먼저 캐릭가 이동하게 만들고 싶다고 할 때 핵심 동작인 캐릭터 이동을 추상화 한다고 하면, 한명의 캐릭터가 여러개의 이동 동작을 쉽게 할 수 있습니다. 글로만 읽어서는 이해가 쉽지 않으니 예시코드를 제공하겠습니다.

// 캐릭터 클래스 정의
public class Character
{
    // 이동 메서드 정의 (인터페이스 활용)
    private IMovable movementMethod;

    // 이동 메서드 설정
    public void SetMovementMethod(IMovable movementMethod)
    {
        this.movementMethod = movementMethod;
    }

    // 이동 메서드 호출
    public void Move()
    {
        if (movementMethod != null)
        {
            movementMethod.Move();
        }
        else
        {
            Debug.LogError("이동 메서드가 설정되지 않았습니다.");
        }
    }
}

// 이동 메서드를 위한 인터페이스 정의
public interface IMovable
{
    void Move();
}

// 기본 이동 메서드 구현
public class DefaultMovement : IMovable
{
    public void Move()
    {
        Debug.Log("기본 이동 방식으로 캐릭터를 이동시킵니다.");
    }
}

// 더 나은 이동 메서드 구현
public class BetterMovement : IMovable
{
    public void Move()
    {
        Debug.Log("더 나은 이동 방식으로 캐릭터를 이동시킵니다.");
    }
}

// 게임 실행 스크립트
public class Game : MonoBehaviour
{
    // 캐릭터 객체 생성
    Character player;

    void Start()
    {
        // 캐릭터 객체 초기화
        player = new Character();

        // 기본 이동 메서드로 설정
        player.SetMovementMethod(new DefaultMovement());
    }

    void Update()
    {
        // 이동 입력 처리
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 캐릭터 이동
            player.Move();
        }
    }
}

이렇게 인터페이스를 이용하여 이동이라는 개념을 추상화하면, player.SetMovementMethod(new DefaultMovement()); 이와 같이 플레이어에게 움직임을 동적으로 할당해주면 자유자재로 여러 이동을 사용할 수 있게되는 것입니다. 이것이 바로 추상화의 장점입니다. 하지만 단점을 설명했듯이 모든 동작에 추상화를 하려다 보면 더 복잡해지고, 코드의 가독성을 떨어트리기에 핵심적인 개념들을 추상화 하는 것이 매우 중요합니다.


[캐릭터와 NPC의 상호작용코드로 객체지향 이해하기]

// 상호작용 가능한 객체의 인터페이스 정의
public interface IInteractable
{
    void Interact(Character character);
}

// 캐릭터 클래스 정의
public class Character
{
    public void InteractWith(IInteractable interactable)
    {
        // 다른 객체와 상호작용
        interactable.Interact(this);
    }

    public void Talk()
    {
        Debug.Log("캐릭터가 NPC와 대화합니다.");
    }

    public void PerformQuest()
    {
        Debug.Log("캐릭터가 NPC로부터 퀘스트를 받습니다.");
    }
}

// NPC 클래스 정의
public class NPC : MonoBehaviour, IInteractable
{
    public void Interact(Character character)
    {
        Debug.Log("NPC와 상호작용합니다.");

        // NPC와 상호작용하는 특정 동작 수행
        character.Talk();
        character.PerformQuest();
    }
}

// 게임 실행 스크립트
public class Game : MonoBehaviour
{
    Character player;
    NPC npc;

    void Start()
    {
        player = new Character();
        npc = new NPC();
    }

    void Update()
    {
        // 플레이어 입력에 따라 상호작용 실행
        if (Input.GetKeyDown(KeyCode.E))
        {
            player.InteractWith(npc);
        }
    }
}
  1. IInteractable 인터페이스는 캐릭터와 NPC가 상호작용할 수 있는 메서드 Interact를 정의합니다.
  2. Character 클래스는 IInteractable 인터페이스를 구현하지 않지만, 다른 객체와 상호작용할 수 있도록 InteractWith 메서드를 갖습니다. 이 메서드는 IInteractable 인터페이스를 구현한 객체와 상호작용합니다.
  3. NPC 클래스는 IInteractable 인터페이스를 구현하여 상호작용 가능한 객체로 만듭니다. Interact 메서드에서는 NPC와 상호작용하는 동작을 수행합니다.
  4. Game 클래스에서는 캐릭터와 NPC를 생성하고, 플레이어의 입력에 따라 캐릭터와 NPC가 상호작용하도록 합니다.

[마무리 글]

C#이나 유니티를 배우면 객체지향 이라는 개념은 꼭 알아두셔야 코드를 작성하실 때 완성도가 높은 코드를 작성하실 수 있습니다. 이를 제대로 이해하지 못하고 코드를 작성하다보면 코드양이 많아지게 되고 이것은 곧 기본기가 없는 개발자처럼 인식되기 때문에 이번에 제가 소개해드린 예시코드를 잘 숙지해 두시기 바랍니다. 또한 객체지향에 너무 몰입한 나머지 너무 과도하게 추상화나 코드 설계에 집중하다보면 오히려 생산성도 떨어지게 되고 복잡한 코드가 나올 수 있으니 주의 하시기 바랍니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다