51. 장비 젬 소켓 장착/제거의 동시 처리 (젬 복제·유실, C#)

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// 시나리오: MMORPG 장비 소켓(보석/젬) 시스템.
//   - 장비(Equipment)에는 여러 개의 소켓 슬롯이 있고, 각 슬롯에 젬(Gem)을 끼우거나 뺄 수 있다.
//   - 젬을 소켓에 장착하면 인벤토리의 젬 하나가 소모되어 장비 슬롯으로 옮겨진다.
//   - 젬을 소켓에서 제거하면 장비 슬롯의 젬이 다시 인벤토리로 돌아온다.
//   - UI 여러 창(장비창/보석 가공창)에서 같은 장비의 같은 슬롯에 대해 장착/제거 요청이
//     "거의 동시에" 서버로 들어올 수 있다(더블클릭, 매크로, 창 2개).
//
// 요구사항:
//   1) 하나의 젬은 인벤토리 또는 특정 소켓 슬롯 중 "정확히 한 곳"에만 존재해야 한다(복제/유실 금지).
//   2) 같은 슬롯에 대한 장착/제거가 동시에 처리돼도 인벤토리 수량과 슬롯 상태가 일관돼야 한다.
//   3) 빈 슬롯에 제거를, 찬 슬롯에 장착을 시도하면 거부한다.
//
// 과제: 아래 SocketService 를 검토하고, 동시 요청 시 요구사항 1~2가 깨지는 지점을 찾아
//       (A)(B) 마커를 근거로 진단하고, 정확한 수정안과 더 나은 설계를 제시하라.

using System.Collections.Generic;

public sealed class Gem
{
    public int GemId;
    public int GemType;
}

public sealed class Equipment
{
    public int EquipId;
    public Gem[] Sockets;               // null = 빈 슬롯
    public Equipment(int slots) { Sockets = new Gem[slots]; }
}

public sealed class Inventory
{
    private readonly List<Gem> _gems = new List<Gem>();
    public void Add(Gem g) { _gems.Add(g); }
    public Gem Take(int gemType)
    {
        for (int i = 0; i < _gems.Count; i++)
        {
            if (_gems[i].GemType == gemType)
            {
                Gem g = _gems[i];
                _gems.RemoveAt(i);
                return g;
            }
        }
        return null;
    }
    public int Count => _gems.Count;
}

public sealed class SocketService
{
    private readonly Dictionary<int, Equipment> _equips = new Dictionary<int, Equipment>();
    private readonly Inventory _inv;

    public SocketService(Inventory inv) { _inv = inv; }

    public void Register(Equipment e) { _equips[e.EquipId] = e; }

    // 젬 장착: 인벤토리의 젬을 빈 소켓으로 옮긴다.
    public bool SocketGem(int equipId, int slot, int gemType)
    {
        Equipment e = _equips[equipId];
        if (e.Sockets[slot] != null)          // (A) 슬롯이 비었는지 검사
            return false;

        Gem g = _inv.Take(gemType);
        if (g == null)
            return false;

        e.Sockets[slot] = g;                  // (B) 빈 것을 확인한 슬롯에 장착
        return true;
    }

    // 젬 제거: 소켓의 젬을 인벤토리로 되돌린다.
    public bool UnsocketGem(int equipId, int slot)
    {
        Equipment e = _equips[equipId];
        Gem g = e.Sockets[slot];              // (A) 슬롯의 젬을 읽어둠
        if (g == null)
            return false;

        _inv.Add(g);
        e.Sockets[slot] = null;               // (B) 되돌린 뒤 슬롯 비움
        return true;
    }
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.