Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Referencing the same object in several collections by interface

Tags:

c#

interface

Warning: this question uses an analogy to RPG as an example.

Let's say I'm making this RPG I've been dreaming of, using C#. When the player enters battle, there is some kind of battlefield appearing, that holds references to every elements relevant to the battle, like the various opponents and items available on the battlefield.

Now all those elements have one but several roles: for instance, an Ally (which is a Fighter through direct inheritance) is able to move on the battlefield, to issue commands or to be targeted by ennemies.

Now that mighty sword in the stone has also a few roles in battle. Obviously it can't move nor issue commands, but it can still be targeted, and it can (hopefully) be lifted or used.

All these behaviors are represented by interfaces in my code, so that all objects with the same behaviors can be used regardless of the object that implements it.

Code example:

public class Ally : Fighter, IMovable, IActor, ITarget
{
    // IMovable implementation
    public void Move(...) { ... }

    // IActor implementation
    public bool HasInitiative { get { ... } }

    public ICommand IssueCommand(...) { ... }

    // ITarget implementation
    public void OnTarget(...) { ... }

    // other code ...
}

public class MightySword : ITarget, ILiftable, IUsable
{
    // ITarget implementation
    public void OnTarget(...) { ... }

    // ... other interface implementation

    // other code ...
}

Now my question is the following: How should my Battlefield class holds its references to all these objects? I came up with two possible solutions, but at this point it's not clear which one is the best, and whether better solutions are to be found.

Solution A:

Reference several time, access directly: Using this solution, each object is referenced once per interface it implements, so my battlefield class would look like below:

public class Battlefield : ...
{
   IList<IMovable> movables;
   IList<ITarget> targets;
   IList<IActor> actors;
   // ... and so on
}

and the Battlefield.Update method could look this way:

Update(...) 
{
  // Say every actor needs to target somebody else
  // it has nothing to do in the Update method,
  // but this is an example
  foreach (IActor actor in actors) 
  {
    if (actor.HasInitiative) 
    {
      ITarget target = targets[actor.TargetIndex];
      target.OnTarget(actor.IssueCommand(...));
    }
  }
}

This solution allows us to have a direct access to the objects' various behavior, but it introduces redundancy, as an Ally would have to be referenced into movables, targets and actors . This redundancy means a bigger memory footprint and will make adding and removing an object to the battlefield a lot more complicated (each type should now from which collections it has to be added/removed).

Also, adding a new interface means adding a new collection to hold the item and manage the corresponding code.

Solution B:

Reference once, filter access: Using this solution, all objects derive from a single class or interface (something akin to IBattleElement), and are stored in one single collection. When using the elements, they are filtered according to their types:

Update(...)
{
    IList<IActor> actors = elements.OfType<Actor>();
    foreach (IActor actor in actors) 
    {
     ITarget target = elements.OfType<Target>()[actor.TargetIndex];
     target.OnTarget(actor.IssueCommand(...));
    }
}

There. No more redundancy, but I dislike using a "typeof" construct and I generally try to avoid it. Obviously this is worse regarding runtime performance.

I implemented solution A in my toy project, but there is nothing performance critical in this project. The answer should consider not only performance (both memory wise and CPU wise), but also what offers the best design for the operations me or my designers might have to perform, such as adding new classes of BattleElement or new interfaces for new behaviors, adding and removing instances of objects from a Battlefield, and more generally use them in the game logic.

I apologize for the general silliness of my examples, I hope my English is good enough, and that my question carries at least a little bit of interest and doesn't indicate an overall silliness in the design itself.

like image 850
dureuill Avatar asked Sep 24 '13 18:09

dureuill


People also ask

Can an object have multiple references?

Can a certain object have different addresses? It means that the reference (pointer) value can be stored in many places. We call each copy of the pointer a reference. So there are many references.

Can multiple object variables contain references to the same object?

Yes, two or more references, say from parameters and/or local variables and/or instance variables and/or static variables can all reference the same object.


Video Answer


1 Answers

Solution A, but don't discount querying the entire list of instances at once if there is a reason to do so.

In general (big generalization, but with significant precedent), you will achieve the greatest flexibility by working with the least derived type possible, that is, just the interface you care about. You will likely get the best performance from working with and grouping instances ahead of time rather than splitting them out after the fact.

Whether or not that performance gain matters is a different question. Since you are probably talking about 10s or 100s of objects (not millions), performance wouldn't be my first concern.

I would shy away from anything which requires real-time type interrogation from a design standpoint (but that's a matter of opinion).

As with performance, profile memory before designing around it. If you are using Visual Studio, you have great tools available to you for doing so. And presumably you aren't copying these instances, so the overhead is minimal anyhow.

A hybrid approach could also make sense.

  • Add/remove from a single collection.
  • Expose both the collection and filtered, read-only collections of a specific type.
  • Only (re)build those filtered collections when needed.

public class Battlefield
{
    private readonly IList<IBattleElement> _all = new List<IBattleElement>();
    private IReadOnlyList<IActor> _actors;
    private bool _isDirty;

    /// <summary>
    ///     Gets a list of all elements.
    /// </summary>
    public IReadOnlyList<IBattleElement> All
    {
        get { return _all; }
    }

    /// <summary>
    ///     Gets a filtered list of elements that are <c>IActor</c>.
    /// </summary>
    public IReadOnlyList<IActor> Actors
    {
        get
        {
            if( _isDirty || _actors == null )
            {
                _actors = All.OfType<IActor>().ToList();
            }

            return _actors;
        }
    }

    /// <summary>
    ///     The one and only way to add new elements. This could be made thread-safe if needed.
    /// </summary>
    /// <param name="element"></param>
    public void AddElement( IBattleElement element )
    {
        _all.Add( element );
        _isDirty = true;
    }
}

...and that my question carries at least a little bit of interest and doesn't indicate an overall silliness in the design itself.

Not silly at all.

like image 96
Tim M. Avatar answered Oct 13 '22 08:10

Tim M.