TextRPG의 모든 도전 내용까지 구현을 완료하였고, 최종적으로 Main 브렌치에 병합을 완료하였습니다.
또한 기존의 프로젝트 구성에서 조금 변경점이 있었습니다
interface IGameStateHandler
{
void Handle(GameLoop context); // 또는 필요한 매개변수를 더 넣어도 됨
}
class GameLoop
{
// 플레이어 변수
public Player myPlayer;
//상태 변수
GameState state = GameState.None;
int waitTime = 100;
//게임 매니져
public Shop shop = new Shop();
public Inventory inventory = new Inventory();
public Database database = new Database();
public SaveManager saveManager = new SaveManager();
//던전관련 변수
public DungeonResultData dungeonResultData { get; set; } = new DungeonResultData();
static bool? isRestore = null;
public void Run()
{
Init();
InitializeStateHandlers();
while (true)
{
if (stateHandlers.TryGetValue(state, out var handler))
{
handler.Handle(this);
}
else
{
Thread.Sleep(waitTime);
}
}
}
private Dictionary<GameState, IGameStateHandler> stateHandlers;
private void InitializeStateHandlers()
{
stateHandlers = new Dictionary<GameState, IGameStateHandler>
{
{ GameState.SetChar, new SetCharHandler() },
{ GameState.Town, new TownStateHandler() },
{ GameState.CheckStat, new CheckStatHandler() },
{ GameState.Inventory, new ShowInventoryHandler() },
{ GameState.Equip, new EquipInventoryHandler() },
{ GameState.Shop, new ShowShopHandler() },
{ GameState.Buy, new BuyShopHandler() },
{ GameState.Sell, new SellShopHandler() },
{ GameState.Dungeon, new ShowDungeonHandler() },
{ GameState.DungeonResult, new DungeonResultHandler() },
{ GameState.Restore, new GoRestoreHandler() },
};
}
}
public interface ISave<T>
{
void Save(T data);
T Load();
}
public class ItemSaveData
{
public int Id { get; set; }
public bool IsEquipped { get; set; }
}
public class InventorySave : ISave<Dictionary<int, Item>>
{
private const string SavePath = "inventory_save.json";
public void Save(Dictionary<int, Item> inventory)
{
var dto = ConvertToDTO(inventory);
var json = JsonSerializer.Serialize(dto, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(SavePath, json);
}
public Dictionary<int, Item> Load()
{
if (!File.Exists(SavePath)) return null;
var json = File.ReadAllText(SavePath);
var dto = JsonSerializer.Deserialize<InventorySaveData>(json);
return ConvertToInventory(dto);
}
private InventorySaveData ConvertToDTO(Dictionary<int, Item> inventory)
{
var dto = new InventorySaveData();
foreach (var value in inventory)
{
dto.Items.Add(new ItemSaveData
{
Id = value.Key,
IsEquipped = value.Value._isEquip
});
}
return dto;
}
private Dictionary<int, Item> ConvertToInventory(InventorySaveData data)
{
// 1) 기존 인벤토리/장착 컬렉션 초기화
var invenDict = new Dictionary<int, Item>();
GameManager.gameLoop.inventory.GetEquipDict().Clear();
// 2) 각 DTO로부터 Item 인스턴스 생성
foreach (var itemDto in data.Items)
{
var item = Item.Create(itemDto.Id);
if (item == null)
continue;
invenDict[itemDto.Id] = item;
GameManager.gameLoop.shop.SetItemBuy(item._id);
if (itemDto.IsEquipped)
{
item.TogleEquipState();
GameManager.gameLoop.inventory.GetEquipDict()[item._itemType] = item;
GameManager.gameLoop.inventory.ActiveItemEffect(item);
}
}
return invenDict;
}
}
기존에 작성했던 코드들이 대부분 Main 함수에 직접 연결해 있어서 모두 Static 으로 선언된 문제가 있었고, 다른 한편으로는 많은 Manager들이 싱글턴으로 인스턴스 생성된 문제가 있었습니다.
internal class GameManager
{
public static GameLoop gameLoop;
static void Main(string[] args)
{
gameLoop = new GameLoop();
gameLoop.Run();
}
}
SOLID 원칙을 잘 지키면서 개발을 진행해보고자 했으나, 실상 잘 되지 않았습니다. 특히나 의존 관계를 설정하는 부분이 아직 미숙하여 더 노력이 필요합니다. 아래의 나용은 SOLID 원칙에서의 의존 관계 역전의 원칙에 대한 설명입니다.
의존관계 역전 원칙(Dependency Inversion Principle)은 객체 지향 설계의 SOLID 원칙 중 하나로, 상위 모듈이 하위 모듈에 의존하지 않도록 설계를 유도하는 원칙입니다.
고수준 모듈은 저수준 모듈에 의존해서는 안 된다. 둘 다 추상화에 의존해야 한다.
추상화는 구체적인 사항에 의존해서는 안 된다. 구체적인 사항이 추상화에 의존해야 한다.
public class FileLogger {
public void Log(string message) {
// 파일에 로그 기록
}
}
public class OrderService {
private FileLogger logger = new FileLogger();
public void PlaceOrder() {
// 주문 처리 로직
logger.Log("주문이 처리되었습니다.");
}
}
OrderService가 FileLogger라는 구체적인 클래스에 직접 의존 → DIP 위반public interface ILogger {
void Log(string message);
}
public class FileLogger : ILogger {
public void Log(string message) {
// 파일에 로그 기록
}
}
public class OrderService {
private readonly ILogger logger;
public OrderService(ILogger logger) {
this.logger = logger;
}
public void PlaceOrder() {
// 주문 처리 로직
logger.Log("주문이 처리되었습니다.");
}
}
public class ConsoleLogger : ILogger {
public void Log(string message) {
Console.WriteLine(message);
}
}
// 사용하는 코드
var logger = new ConsoleLogger();
var orderService = new OrderService(logger);
orderService.PlaceOrder();
OrderService는 ILogger라는 추상화에만 의존FileLogger는 ILogger의 구현체1. 인터페이스부터 정의한다.
→ 어떤 동작(기능)을 추상화해서 이름만 정해두는 역할.
2. 그 인터페이스를 기반으로 실제 클래스를 구현한다.
→ 예: FileLogger, ConsoleLogger 등
3. 고수준 모듈(비즈니스 로직을 수행하는 클래스)은 인터페이스에만 의존하게 만든다.
→ 직접 구현체(FileLogger)에 의존하지 않는다.
4. 나중에 구현체를 갈아끼우기 쉽게 만든다.
→ 코드 수정 없이도 기능을 교체하거나 테스트용 가짜 객체를 넣을 수 있다.
의존관계 역전 원칙은 소프트웨어의 변경에 강한 구조를 만들기 위해 매우 중요한 설계 원칙입니다. 추상화에 의존하도록 설계함으로써, 모듈 간의 결합도를 낮추고, 확장성과 테스트 용이성을 높일 수 있습니다.