0

So let's say I have two classes, Food and Barrel for example. Barrel contains an array of food private Food[] stores to track what's stored in it. Barrel also contains methods to access and modify stores, such as public void Insert(Food) and public void Remove(Food).

So Barrel can store Food. But I also want Food to know where it's stored in, if anywhere. For example, I want to have a method Food.Rot(), which only rots food if it's not stored.

My current solution is to have a property public Barrel Container { get; set; } in Food that tracks the instance of Barrel it's stored in, and have Barrel.Insert(Food toAdd) set toAdd.Container to itself and Barrel.Remove(Food toRemove) set toRemove.Container to null (under which condition Food.Rot() would actually rot the food).

However, any other class can also access Food.Container, and if they write to it, the original Barrel will still have this Food instance in stores, but Food.Container will no longer reflect that.

I can make the set method ensure that when Food.Container changes, it first calls Container.Remove(this), but I really don't want anything besides Barrel to be able to set Food.Container in the first place.

I could also make it so Food doesn't store its container at all and simply have Rot() call a method bool IsStored() to check every single Barrel.stores to see if the instance of the Food class that called it was stored inside, that way there's no two-way malarkey, but that seems just terrible for optimization.

It feels like somewhere along this design I've violated OOP and I expect that ideally this whole situation needs to be redesigned. I'm more than open to such feedback as well.

  • 1
    You could just achieve this using composition instead, most modern game engines use composition instead of inheritance. This way food can query the composition to determine if there is a container or not when you ask it to rot, or you can simply remove the rot component from food when it's added to a container and add it back when it's removed. It sounds a bit dwarf fortress... Which is awesome. – Charleh Jun 05 '21 at 21:30
  • @Charleh But wouldn't that still have the problem of someone externally adding/removing the rot behavior afterwards? Or are you referring to composing the Food object when adding it to the barrel? – Mariano Luis Villa Jun 05 '21 at 21:41
  • @Charleh I've never heard of composition before so thank you, but I'm not sure I understand it yet. I want food to exist independently of barrels; I just want barrels to be able to store food. There's no inheritance going on right now. – Justin Iaconis Jun 05 '21 at 21:56
  • 1
    https://gameprogrammingpatterns.com/component.html have a read. With composition in mind, a barrel would be a game object. The lifecycle of the food (decomposition etc) could be handled through a `Rot` component. Adding and removing items to/from the barrel would check to see if they are `Food` items to prevent non food being stored and disable/reenable the `Rot` component respectively. Basically the barrel determines that the food shouldn't rot, rather than the food querying its environment. – Charleh Jun 06 '21 at 15:48

1 Answers1

1

When Food added into a Barrel you must modify the Barrel attribute of the Food. Also when the Barrel attribute is set you must add the food into the Barrel if not exists. Checking exists is important because otherwise, these two will call each other to cause a StackOverflow Exception.

You must also repeat the same approach when removing. Here is the sample you need:

public class Food
{
    public string Name { get; set; }
    private Barrel _barrel;
    public Barrel Barrel
    {
        get
        {
            return _barrel;
        }
        set
        {
            if (_barrel != null && _barrel != value)
            {
                _barrel.Remove(this);
            }
            if (value != null)
            {
                value.Insert(this);
            }
            _barrel = value;
        }
    }
    public override string ToString()
    {
        return Name+"@"+ Barrel??"";
    }
}
public class Barrel
{
    public string Name { get; set; }
    public List<Food> Stores { get; set; } = new List<Food>();

    public void Insert(Food food)
    {
        if (!Stores.Contains(food))
        {
            Stores.Add(food);
            food.Barrel = this;
        }
    }
    public void Remove(Food food)
    {
        if (Stores.Contains(food))
        {
            Stores.Remove(food);
            food.Barrel = null;
        }
    }
    public override string ToString()
    {
        return Name;
    }
}

Test code:

    Barrel b0 = new Barrel() { Name = "Barrel 0" };
    Barrel b1 = new Barrel() { Name = "Barrel 1" };
    //Use insert method.
    Food fish = new Food() { Name = "Fish" };

    //I will insert fish to b0 first.
    b0.Insert(fish);

    b1.Insert(fish);
    //Assign barrel directly
    Food chicken = new Food() { Barrel = b0, Name = "Chicken" };
    //then change barrel
    chicken.Barrel = b1;

    Debug.WriteLine("Barrel 1:"+ string.Join(",",b1.Stores));

    //Modify Barrel attribute
    chicken.Barrel = null;
    Debug.WriteLine("Barrel 1:" + string.Join(",", b1.Stores));

    //Remove from stores
    b1.Remove(fish);
    Debug.WriteLine("Barrel 1:" + string.Join(",", b1.Stores));

Output:

Barrel 1:Fish@Barrel 1,Chicken@Barrel 1
Barrel 1:Fish@Barrel 1
Barrel 1:
aliassce
  • 1,197
  • 6
  • 19
  • So basically what you've done is make it so that you can also insert food into a barrel by setting the barrel property of food to a value? This way information is all updated correctly in both directions. That's great, but there's still the problem of any class being able to set food's barrel property. – Justin Iaconis Jun 05 '21 at 22:14
  • That's true. If the Barrel.Insert() can change the Food's Barrel Property other classes will be able to change too. By this approach if anywhere the Barrel property of Food is changed, this Food will also be removed form that Barrel. For access limitation you may use **protected** keyword for derived classes but it won't help here. – aliassce Jun 05 '21 at 22:21
  • So there's no way to allow accessibility for specific classes? I took a semester of c++ a little while ago and there was something about the 'friend' keyword, but I don't actually know anything about it. – Justin Iaconis Jun 05 '21 at 22:27
  • There may be some workarounds, but basically, it's not a desired option for most people except derived classes. https://stackoverflow.com/questions/203616/why-does-c-sharp-not-provide-the-c-style-friend-keyword – aliassce Jun 05 '21 at 22:37
  • There's no way to prevent a class from accessing all public or internal properties of any other given class in the same assembly. To me this doesn't sound like an accessibility problem but a design problem (that would be solved with composition and isn't that difficult to get your head around). – Charleh Jun 06 '21 at 16:04