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"?
2 Answers 2
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.
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.
-
Maybe it is a code smell. Consider
System.Windows.Forms.Control
: It has aControls
and aParent
property. This is somewhat similar, although it is the same class, and not two different classes.Martin– Martin04/04/2014 11:51:34Commented 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 thanparent
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– Telastyn04/04/2014 13:28:52Commented Apr 4, 2014 at 13:28 -
I think this is an interesting aspect.Martin– Martin04/04/2014 14:51:44Commented Apr 4, 2014 at 14:51
i.Container == c
and!c.Items.Contains(i)
. So, yes, one solution (that is not possible without friends) is that onlyContainer
has write access toItem.Container
Item
sContainer
.