객체지향 프로그래밍(OOP)에 있어 항상 등장하는 개념들... 클래스인터페이스

이 둘의 차이점은 한번에 이해해보자!

 

Q1. 클래스(Class)인터페이스(Interface)의 차이

클래스와 인터페이스의 차이

 

클래스객체를 만들려는 설계도, 틀이라고 하면

인터페이스 클래스가 지켜야할 규칙을 정의한다.

 

예시로 자동차 클래스자동차 속성(색깔, 속도, 연료 등)이 있고,

자동차 인터페이스자동차가 반드시 있어야 하는 기능(시동 on / off, 이동기능들)들을 정의하는 것!

 


- 코드로 보는 클래스의 특징 (c#)

예시 코드는 모든 게임 유닛의 Base가 되는 BaseUnit 클래스가 있고

BaseUnit을 상속받아서 각각 플레이어 캐릭터적 캐릭터를 만드는 간단한 예시이다.

 

상황은 간단하다. 플레이어랑 적 캐릭터가 서로를 향해 공격을 한다!

    // 기본적인 유닛 클래스
    class BaseUnit
    {
        public string Name { get; set; }
        public int maxHp { get; set; }
        public int curHp;

        public BaseUnit(string name, int health)
        {
            Name = name;
            maxHp = health;
            curHp = maxHp;
        }

        public virtual void TakeAction(BaseUnit target)
        {
            Console.WriteLine($"{Name}의 행동");
        }

        public void TakeDamage(int damage)
        {
            curHp -= damage;
            Console.WriteLine($"{Name}가 {damage}의 피해를 입었다. {Name}의 체력: {curHp}\n");
        }
    }


    // 플레이어 클래스 (BaseUnit 상속)
    class Player : BaseUnit
    {
        public Player(string name, int health) : base(name, health) { }

        public override void TakeAction(BaseUnit target) 
        {
            Attack(target);
        }

        public void Attack(BaseUnit target)
        {
            Console.WriteLine($"{Name}가 {target.Name}을 공격했다!");
            target.TakeDamage(10);
        }
    }

    // 적 캐릭터 클래스 (BaseUnit 상속)
    class Enemy : BaseUnit
    {
        public Enemy(string name, int health) : base(name, health) { }

        public override void TakeAction(BaseUnit target)
        {
            Console.WriteLine($"{Name}의 공격! {target.Name}는 피해를 입었다.");
            target.TakeDamage(5);
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Player player = new Player("주인공", 100);
            Enemy enemy = new Enemy("고블린", 50);

            player.TakeAction(enemy);
            enemy.TakeAction(player);
        }
    }

 

클래스속성과 메서드를 만들 수 있다.

사용할 때는 객체를 생성해서 사용하며, 상속을 통해서 클래스를 확장시킬 수 있다.


- 코드로 보는 인터페이스의 특징

이번에는 피해를 받는 인터페이스를 정의하고, 피해 인터페이스BaseUnit이 상속받았다.

 

상황은 이번에도 똑같이 서로를 향해 공격을 가하는데 적 캐릭터가 가하는 공격은 방패가 피해를 받는다!

namespace Class_Interface
{

    // 공격을 받는 인터페이스 정의
    interface IAttackable
    {
        void TakeDamage(int damage);
    }

    // 기본적인 유닛 클래스
    class BaseUnit : IAttackable
    {
        public string Name { get; set; }
        public int maxHp { get; set; }
        public int curHp;

        public BaseUnit(string name, int health)
        {
            Name = name;
            maxHp = health;
            curHp = maxHp;
        }

        public virtual void TakeAction(IAttackable target)
        {
            Console.WriteLine($"{Name}의 행동");
        }

        public void TakeDamage(int damage)
        {
            curHp -= damage;
            Console.WriteLine($"{Name}가 {damage}의 피해를 입었다. {Name}의 체력: {curHp}\n");
        }
    }


    // 플레이어 클래스 (BaseUnit 상속)
    class Player : BaseUnit
    {
        public Player(string name, int health) : base(name, health) { }

        public override void TakeAction(IAttackable target) 
        {
            Attack(target);
        }

        public void Attack(IAttackable target)
        {
            Console.WriteLine($"{Name}의 공격!");
            target.TakeDamage(10);
        }
    }

    // 적 캐릭터 클래스 (BaseUnit 상속)
    class Enemy : BaseUnit
    {
        public Enemy(string name, int health) : base(name, health) { }

        public override void TakeAction(IAttackable target)
        {
            Console.WriteLine($"{Name}의 공격!");
            target.TakeDamage(5);
        }
    }

    // 방어구 클래스 (IAttackable 인터페이스 구현)
    class Shield : IAttackable
    {
        public int Durability { get; set; }

        public Shield(int durability)
        {
            Durability = durability;
        }

        public void TakeDamage(int damage)
        {
            Durability -= damage;
            Console.WriteLine($"방패가 {damage}의 피해를 흡수했습니다! 남은 내구도: {Durability}");
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            Player player = new Player("주인공", 100);
            Enemy enemy = new Enemy("고블린", 50);
            Shield shield = new Shield(30);

            player.TakeAction(enemy);
            enemy.TakeAction(shield);
        }
    }
}

인터페이스의 실행 결과

 

클래스와 다른점은 인터페이스는 공격 선언만 하고, 내용은 없다.

하지만 클래스인터페이스를 구현 (implement) 한다면공격 기능은 반드시 구현시켜야 한다!


 

상속(Extends)구현(Implements)의 차이점만 이해한다면 좀 더 알기 쉬워진다

  클래스 상속 인터페이스 구현
관계 부모-자식 관계 (is-a 관계) 능력을 받는다 (Can-do 관계)
개수 단일 상속만 가능 (1개) 다중 구현이 가능 (여러개)
내용 부모의 속성/메서드를 물려받음 메서드 선언만 약속하고
구현은 하위에서 담당!
목적 코드 재사용성 (확장성) 공통된 행위 약속

 

 

아주 간단하게 정리하자면

 

  • 클래스는 "나는 누구?이다." (플레이어, 적 개체를 생성한다)
  • 인터페이스는 "나는 이 무언가?를 할 수 있다." ('공격을 받을 수 있다' 정의를 설정한다)

 

 

- 전략 패턴(Strategy Pattern)이란?

객체가 수행하는 여러가지 행동(행위) 중에서 상황에 맞는 적절한 전략을 선택해서 실행하는 패턴을 말한다.

쉽게 말해 "알고리즘을(행위를) 통째로 바꿔서 적용할 수 있는 패턴" 이라고 생각하면 된다.

 

좀 더 쉽게 이해하기 위해서 게임으로 예시를 들어보자!

 

전략 패턴의 게임 예시

 

RPG 게임에서 적 캐릭터(몬스터)가 있다고 가정해보자.

몬스터는 플레이어와의 거리가 가까우면 근접으로 공격하고 멀다면 원거리에서 공격한다.

이 몬스터만이 가지고 있는 고유의 패턴을 만들때 전략 패턴을 사용하는 것!

 

또는 보스 몬스터만이 가지고 있는 패턴이 존재할 수 있는데 이 때에도 전략 패턴을 사용할 수 있다.

 

  • HP 100%~50% → 원거리 공격 전략
  • HP 50%~20% → 돌진 공격 전략
  • HP 20% 이하 → 발악 기술 전략

이런 식으로 패턴을 설계해서 구현할 수 있는 것!


 

- 그렇다면 전략 패턴을 어떻게 사용하나요?

이번에도 간단한 c# 게임 코드를 통해서 예를 들어보자!

1. 우선 기술 전략 인터페이스를 먼저 정의한다!

// 스킬 전략
public interface ISkillStrategy
{
    string SkillName { get; }
    void Skill();
}

2. 전략 인터페이스상속받아 다양한 스킬 전략을 만든다!

// 근접 기술 전략
public class Skill_Melee : ISkillStrategy
{
    public string SkillName => "근접 기술 A";

    public void Skill()
    {
        Console.WriteLine($"{SkillName}로 공격!");
    }
}

// 원거리 기술 전략
public class Skill_Range : ISkillStrategy
{
    public string SkillName => "원거리 기술 B";

    public void Skill()
    {
        Console.WriteLine($"멀리서 {SkillName}로 공격!");
    }
}

// 치유 기술 전략
public class Skill_Heal : ISkillStrategy
{
    public string SkillName => "치유 기술 C";

    public void Skill()
    {
        Console.WriteLine($"{SkillName}로 상처를 치료!");
    }
}

 


3. 적용할 대상에게(클래스한테) 전략을 적용시킨다.

    // 몬스터에게 전략 적용
    public class Enemy1 { 
        private string enemyName = "고블린";
        private ISkillStrategy skillStrategy;

        // 전략 변경
        public void SetSkillStrategy(ISkillStrategy skillStrategy)
        {
            this.skillStrategy = skillStrategy;
        }

        // 스킬 사용
        public void takeSkill()
        {
            Console.WriteLine($"\n{enemyName}이 {skillStrategy.SkillName} 기술을 사용했다!");
            skillStrategy.Skill();
        }
    }

3-1. 사용 테스트 1

    internal class Program
    {
        static void Main(string[] args)
        {
            Enemy1 enemy = new Enemy1();

            // 근접 기술 사용
            enemy.SetSkillStrategy(new Skill_Melee());
            enemy.takeSkill();

            // 전략 수정 후 원거리 기술 사용
            enemy.SetSkillStrategy(new Skill_Range());
            enemy.takeSkill();

            // 전략 수정 후 아군 치유 기술 사용
            enemy.SetSkillStrategy(new Skill_Heal());
            enemy.takeSkill();
        }
    }

 

테스트 결과

테스트 결과

 

이런 식으로 행동을 상황에 맞게 교체가능한 전략으로 분리해주는 패턴이다!

이제 마지막으로 상황에 맞게끔 사용한다고 예시를 들어보자면

3-2. 사용 테스트 2

    internal class Program
    {
        static void Main(string[] args)
        {
            Enemy1 enemy = new Enemy1();

            int enemyMaxHp = 30;        // 최대 체력
            int enemyCurHp = 14;        // 현재 체력
            int playerDistance = 5;     // 플레이어과의 거리

            // 상황에 따른 전략 결정
            if (enemyCurHp <= enemyMaxHp * 0.5)
            {
                enemy.SetSkillStrategy(new Skill_Heal());
            }
            else if (playerDistance <= 5)
            {
                enemy.SetSkillStrategy(new Skill_Melee());
            }
            else
            {
                enemy.SetSkillStrategy(new Skill_Range());
            }

            // 전략에 맞는 기술 사용
            enemy.takeSkill();
        }
    }

테스트 결과 2

 

이렇게 현재 체력이 최대 체력의 절반 이하라서 전략을 치유 스킬 전략으로 바꿔서 사용한 것이다!

 

그 외에도 스킬에 재사용 대기시간을 부여하거나,

랜덤값을 줘서 무작위 기술을 사용하도록 설계하거나

전략을 원하는데로 교체하도록 구현할 수 있다!

+ Recent posts