Search code examples
c#oopdesign-patternsmonogame

How would I have properties of an object modified only by components of that object in C#?


So let's say I have an interface in my game, IItem. IItem represents an object with a render component, a weight, and an attached "process". The process may be attached by someone using some ability to affect the item. Maybe something like an "enchantment" in an RPG. Let's say I want that attached process to be able to modify the weight of the IItem. However, I only want a process which is a component of the IItem to be able to do so. Objects outside of the IItem need to be able to get the weight though.

Do I implement a Weight property, with just a getter in the interface? Then does the base class implement a "SetWeight" method, which is declared internal to the Item namespace? That still does not really guarantee that only a component owned by the particular IItem can affect it of course, but at least something in the Input namespace cannot directly change the weight of an item. In C I could do something similar to linked lists in the kernel which can get their container, this would give me a way to make sure the weight and the process had the same container object, however, I do not see an easy way to do this in C#. Furthermore, I think it kind of damages the component based design for a component to have to have a reference back to its parent.

This is a general problem I am running in to as I design my interfaces and classes.


Solution

  • Have you considered using a Dictionary, instead of properties? Something like this:

    // Item base class
    public Dictionary<string, object> Attributes { get; private set; }
    
    public List<Process> Enchantments { get; private set; }
    
    public virtual T Get<T>(string identifier)
    {
        var att = this.Attributes.FirstOrDefault(att => att.Key == identifier);
        if (att == null) throw new MissingAttributeExeption(identifier); // Or perhaps just return default(T)
        if ((att.Value is T) == false) throw new InvalidAttributeCastException(identifier, typeof(T));  
    
        var value = att.Value;
        foreach (var ench in this.Enchantments)
        {
            ench.Modify(identifier, ref value);
        }
    
        return value as T; // Maybe you need to cast value to Object, and then to T, I can't remember.
    }
    
    // Process class
    
    public string ValueToModify { get; set }
    
    public virtual void Modify(string identifier, ref object value)
    {
        if (identifier != this.ValueToModify) return;
    
        // In an inherited class, for example a Weightless-Enchantment: Halfs all weight
        var castedVal = value as int
        value = castedVal / 2;
        // Now this one item weights 50% of normal weight, and the original value is still stored in Item's Attributes dictionary.
    }
    
    // Some random class
    
    public void Update()
    {
        var totalWeight = 0;
        foreach (var item in this.itemCollection)
        {
            int weight = item.Get<int>("Weight");
            totalWeight += weight;
        }
        Console.WriteLine("The player weights: {0}", totalWeight);
    }
    

    Obviously this means you can't really hardcode attributes, but... Do you ACTUALLY want to do this, in the long run? I mean once you start adding Intelligence, Agility, Strength, Stamina, Endurance, etc.

    I know this doesn't solve your posed question, but I should think it's a pretty decent alternative.