想要避免在复杂的只读 class(游戏状态)中返回可写引用

Want to avoid returning writable references inside of a complex read-only class (game state)

我正在写一个小游戏,游戏有一个 State class 来跟踪游戏状态。 State class 只能由 Commands(命令模式)修改。 State class 包括其他 classes 的列表 - 例如Faction class,其中包含资源等成员、拥有的 Unit 列表等

如何使 State 的深层内部结构可从其他 class 中读取,同时又不泄漏 State 本身内部的可写引用?

目前,我有像 State.GetOwnerOfUnitAtLocation(x) 这样的专用吸气剂,它只有 return 个安全值(int factionid 等),但我开始需要很多这样的吸气剂,而且class 变得非常笨拙。我更愿意将这些方法放在更合适的位置(Map.Units.GetOwner(x) 或其他),但我不知道如何以安全的方式将 State 的内部结构暴露给其他 classes .

相关地,Command 是目前存在于 State class 内部的接口,以及实现它的所有实际命令,因此它可以修改私有成员State。有没有更好的方法来实现这个?


编辑:从 State 中选择代码来说明第一个问题:

public partial class State
{
    public int Turn {get; private set;} = 1;
    
    private Dictionary<Vector2, FactionsMgr.Faction> _unit_map = new Dictionary<Vector2, FactionsMgr.Faction>();

    public int GetUnitRemainingMobility(Vector2 pos)
    {
        if (IsUnitAt(pos))
        {
            FactionsMgr.Faction owner = _unit_map[pos];
            int taken_movement = owner.units[pos]._taken_movement;
            int max_mobility = UnitsMgr.GetMaxMobility(owner.units[pos].type);
            return max_mobility - taken_movement;
        }
        else
        {
            GD.Print("Warning: GetUnitRemainingMobility asked for unknown unit: ", pos);
            return -1;
        }
    }
}

因为 FactionsMgr.Faction 是可变的,我们假设它有这样的可写属性:

class Faction {
    public int Foo { get; set; }
    public string Bar { get; set; }
    public float Baz { get; set; }
    public SomeOtherMutableThing AnotherThing { get; set; }
}

你应该为它创建一个相应的只读接口,并Faction实现它:

interface IReadOnlyFaction {
    // exclude all members that can change Faction's state
    int Foo { get; }
    string Bar { get; }
    float Baz { get; }
    IReadOnlySomeOtherMutableThing AnotherThing { get; }
}

interface IReadOnlySomeOtherMutableThing {
    // do the same thing there...
}

class Faction: IReadOnlyFaction {
    public int Foo { get; set; }
    public string Bar { get; set; }
    public float Baz { get; set; }
    public SomeOtherMutableThing AnotherThing { get; set; }

    // you need an explicit interface implementation here, unless you are using C# 9
    IReadOnlySomeOtherMutableThing IReadOnlyFaction.AnotherThing => AnotherThing;
}

然后,您可以在 State 中声明 IReadOnlyFaction 类型的 public 成员,并将它们委托给 Faction 类型的私有成员。私有成员也是 Command class 将修改的内容。

private Faction someFaction;
public IReadOnlyFaction SomeFaction => someFaction;

这是一般情况。但是,如果您有这些可变类型的 集合 ,例如 _unit_map 的字典,您将需要做更多的工作。

您仍然会有一个 public 只读成员和一个私有可变成员,但委派过程不那么简单。你需要 wrapper.

private Dictionary<Vector2, FactionsMgr.Faction> _unit_map = new();

public IReadOnlyDictionary<Vector2, IReadOnlyFaction> UnitMap;

// ...

// in the constructor of State...

// using the ReadOnlyDictionaryWrapper class from the linked answer
UnitMap = new ReadOnlyDictionaryWrapper<Vector2, FactionsMgr.Faction, IReadOnlyFaction>(_unit_map);