Quintus
Quintus

Reputation: 119

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

I'm writing a little game, and the game has a State class to keep track of game state. The State class is only intended to be modifiable by Commands (command pattern). The State class includes lists of other classes - e.g. a Faction class, which contains members like resources, a list of owned Units, etc.

How can I make the deep innards of State readable from other classes, without also leaking writable references inside of State itself?

Currently, I have specialized getters like State.GetOwnerOfUnitAtLocation(x), which only return safe values (int factionid etc.), but I am beginning to need a lot of these, and the class is getting really unwieldy. I would prefer to have those methods in more appropriate locations (Map.Units.GetOwner(x) or something), but I don't know how to expose the internals of State to other classes in a safe way.

Relatedly, Command is an interface that currently lives inside of the State class, along with all the actual commands that implement it, so that it can modify private members of State. Is there a better way to implement this?


Edit: A selection of code from State to illustrate the first issue:

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;
        }
    }
}

Upvotes: 0

Views: 76

Answers (1)

Sweeper
Sweeper

Reputation: 271830

Since FactionsMgr.Faction is mutable, let's suppose that it has writable properties like this:

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

You should create a corresponding read only interface for it, and make Faction implement it:

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;
}

Then, you can declare public members in State as of type IReadOnlyFaction, and delegate them to a private member of type Faction. The private member is also what the Command class will modify.

private Faction someFaction;
public IReadOnlyFaction SomeFaction => someFaction;

That is the general case. However, if you have collections of these mutable types, like your dictionary of _unit_map, you would need to do a bit more work.

You would still have a public read only member and a private mutable member, but the delegating process is less straightforward. You would need a 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);

Upvotes: 2

Related Questions