0

I have a class Container containing objects of type Item. They are different classes, and especially they have no common base class (e.g. a Container does not itself have a Container).:

class Container
{
    public ICollection<Item> Items { get { /* ... */ } }
}

class Item
{
    public Container Container { get { /* ... */ } }
}

I’d like to enforce that each Item needs to have exactly one Container, and I’d like to make sure there never can be an inconsistent situation such that e.g. i.Container.Items.Contains(i) returns false.

This could be done by e.g. adding a method Container.AddItem that sets the Item’s Container property accordingly or by having a Item.SetContainer method that takes care of Container.Items.

Example:

class Item
{
    private Container container;
    public Container Container
    {
       get
       {
          return container;
       }
       set
       {
           if (container != null)
               container.Items.Remove(this);
           container = value;
           if (value != null)
               value.Items.Add(this);
       }
    }
}

This could be easily done with the concept of a “friend”, but I’m using C# that does not have friends, and thus this example code won’t compile. However, it of course would compile if Container.Items had a public setter, but this again would allow inconsistencies: A “user” of the classes would need to know not to use the setter of Container.Items, but Item.Container instead.

After searching for quite some time, I feel there is no way to enforce this restriction that seems to be so trivial. (Using internal might be a work-around, but no solution.)

Is there an elegant way to enforce this one-container-per-item rule without the concept of “friend”?

6
  • Do you want to only allow Container to have access to Item.Container or what is your problem?
    – hansmaad
    Apr 4, 2014 at 9:35
  • 1
    Can't you just pass the container in the constructor?
    – Andy Hunt
    Apr 4, 2014 at 9:48
  • @hansmaad: I’d like to avoid inconsistent situations like i.Container == c and !c.Items.Contains(i). So, yes, one solution (that is not possible without friends) is that only Container has write access to Item.Container
    – Martin
    Apr 4, 2014 at 10:49
  • @AndyBursh: It must be possible to change an Items Container.
    – Martin
    Apr 4, 2014 at 10:51
  • @Martin OK, so pass it in the constructor and add a setter which checks for nulls?
    – Andy Hunt
    Apr 4, 2014 at 10:53

2 Answers 2

3

You could extract this to a separated service that has the only resposnsibility to track Container<->Item associoations. I think this is a very flexible solution.

class Container<T>
{
    private IItemAssociations associations;
    public Container(IItemAssociations a)
    {
        this.associations = a;
    }

    public void Add(T i)
    {
        if (!associations.IsAddedToAContainer(i))
        {
            DoAdd(i);
            associations.AddAssociation(this, i);
        }
        else
            HandleAlreadyAdded(i);
    }
}

You can have a static constructor method that injects a global association object or use some other injection mechanism.

1

internal would be a fine solution here since container and item are inextricably linked, they're going to be part of the same assembly.

That said, this is a code smell.

Items should generally not know if they belong in a container, let alone being restrained to only one container. This is a coupling nightmare since items will then inevitably go back to their container to look for other items, tightening that relation. Then you get into "how do I inform this item that the peer it cared about has suddenly left the container?" Which makes the container then care about which items care about what other items and on down.

3
  • Maybe it is a code smell. Consider System.Windows.Forms.Control: It has a Controls and a Parent property. This is somewhat similar, although it is the same class, and not two different classes.
    – Martin
    Apr 4, 2014 at 11:51
  • 1
    @Martin - That's why I said code smell rather than anti-pattern. It can certainly be the cleanest way to accomplish a task, especially location sort of containers rather than parent sort of things. But I'd still avoid it if possible. As soon as a child knows about its parent, it's no longer a child but a peer.
    – Telastyn
    Apr 4, 2014 at 13:28
  • I think this is an interesting aspect.
    – Martin
    Apr 4, 2014 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.