세번째 미션을 해결했다.
제네릭과 인터페이스를 써보는건데, List<T> 타입을이용해서 제너릭을 사용하고
플레이어와 몬스터에서 반드시 필요한 변수들에 대한 인터페이스를 만들었다.
미션 3: 제네릭과 인터페이스
- 목표: Generic과 Interface를 활용하여 재사용 가능한 코드를 작성한다.
- 미션:
- IGameCharacter 인터페이스를 만들고, 이름, 체력, 공격력 프로퍼티를 정의해라.
- Player와 Monster 클래스가 이 인터페이스를 구현하도록 수정해라.
- 제네릭 리스트를 사용해 여러 몬스터를 관리하고, 플레이어가 몬스터를 선택적으로 공격할 수 있는 코드를 작성해라.
1. GameManager에서 몬스터를 배열로 Monster[] 로 관리한던걸 List<Monster> 로 바꿨다.
-> 고정된 배열대신 List를 사용.
2. IGameCharacter 인터페이스를 제공해서 필요한 변수들을 무조건 구현하게끔 했고, 이것을 상속함으로써
나중에 한번에 관리하기 편하게 하였다.
3. ItemManager에서 Item을 관리할 때, 나중에 어떤 타입이었는지 알기위해 ItemType 필드를 추가했다.
-> Item으로 관리하며 Use사용할수만있고 정보만 알면된다고 생각했는데, 아이템마다 원래 타입에서만 있는 기능을 사용할 수 없겠다고 판단하여 원래 타입을 알 수 있는 장치를 마련해야겠다고 생각해서 추가했다.
public List<Monster> monsters;
public void Init()
{
Player = new Player();
monsters = new List<Monster>();
for(int i = 0; i < MonsterCount; i++)
{
monsters.Add(new Monster());
}
_boss = new Boss();
}
GameManager에서 바꾼부분이다. 동적으로 Add, remove 가 가능하기 때문에 몬스터 숫자가 늘어나거나 변칙적이 되면
이 방식이 더 좋은 것 같다.
public interface IGameCharacter
{
public int Hp { get; set; }
public int AttackPower { get; set; }
public string Name { get; set; }
}
인터페이스이다. 간단하게 구현했다.
public abstract class Item
{
public string Name { get; protected set; }
public string Description { get; protected set; }
public ItemType Type { get; protected set; }
public virtual void Use()
{
Console.WriteLine($"{Name}아이템을 사용!");
}
};
Item 클래스에 ItemType을 필드로 넣어서 원래 어떤타입이었는지 알수있게 한다.
이것에 대한 유닛테스트도 진행했다.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TextRpgCS;
namespace TextRpgCSTest3
{
[TestClass]
public class UnitTest1
{
// 성공
//[TestMethod]
//public void Item_ShouldStoreCorrectItemType()
//{
// // Arrange
// var item = new HpPortion(); // 체력 물약 생성
// ItemManager.Instance.AddItem(item);
// // Act
// var retrievedItem = ItemManager.Instance.Inventory[item.Name][0];
// // Assert
// Assert.IsNotNull(retrievedItem);
// Assert.AreEqual(ItemType.HpPortion, retrievedItem.Type, "아이템 타입이 일치하지 않음!");
//}
//[TestMethod]
//public void GameManager_ShouldInitializeMonsterList()
//{
// // Arrange
// var gm = GameManager.Instance;
// gm.Init();
// // Act
// int monsterCount = gm.monsters.Count;
// // Assert
// Assert.AreEqual(GameManager.MonsterCount, monsterCount, "몬스터 개수가 올바르지 않음!");
//}
[TestMethod]
public void Player_And_Monster_ShouldImplementIGameCharacter()
{
// Arrange
var player = new Player();
var monster = new Monster();
// Assert
Assert.IsTrue(player is IGameCharacter, "Player가 IGameCharacter를 구현하지 않음!");
Assert.IsTrue(monster is IGameCharacter, "Monster가 IGameCharacter를 구현하지 않음!");
}
}
}
각각 아이템에서 ItemType 필드가 잘 작동하나, 리스트로 바꾼 몬스터리스트가 작동하나, IGameCharacter로 구현한게 잘 작동하나 이다.
미션3에서 근데 의문이었던 점은 IGameCharacter를 만들어서 공통적인 것들을 인터페이스를 상속받게하라는 것이었다.
그렇다면 왜 인터페이스를 사용해야할까? 추상클래스를 부모클래스로 만들어도 되는데?
chatgpt에게 물어본 결과, 인터페이스는 동작을 강제할 때, 그리고 추상클래스는 공통 로직이 필요할때 사용하면 된다고 한다.
예를 들어서, TakeDamage라는 기능만 있어도된다면 인터페이스를 사용하고 TakeDamage에서 반드시 체력을 먼저 깎고
Attack을 해야한다. attack하고 체력을 깎으면 안된다라는 로직을 강제해야하면 추상클래스를 사용하는게 낫다고 한다.
또 내 생각엔 인터페이스는 다중 상속이 가능하기 때문에, 조그만 규모의 인터페이스를 여러개 만들어서 각 클래스에 부품처럼 조립할 때, 더 유리하겠다는 생각이 든다. 추상클래스는 하나 밖에 상속을 못하기 때문이다.
마무리로 내가 직접 제너릭타입의 함수나 더 많은 인터페이스를 앞으로도 확장하면서 사용해봐야겠다.
아직 규모가 작아서 그런지 필요성이 크게 느껴지진 않는다.
'텍스트 Rpg - C#' 카테고리의 다른 글
C#으로 만드는 텍스트 Rpg (5) (0) | 2025.02.05 |
---|---|
C#으로 만드는 텍스트 Rpg (4) (0) | 2025.02.03 |
C#으로 만드는 텍스트 RPG (2) (0) | 2025.01.31 |
C#으로 만드는 텍스트 RPG (1) (0) | 2025.01.25 |
C#으로 만드는 텍스트 Rpg (0) (0) | 2025.01.24 |