BaseUnit > Unit > ContainerUnit
- BaseUnit is the core class.
- Unit adds a ContainerUnit property called Parent.
- ContainerUnit adds a List<Unit> property called Children.
So, all Unit types (including ContainerUnit) must have a parent that is a ContainerUnit. ContainerUnit types can have children that are ContainerUnit types or just Unit types.
So you can have a box of items, some of which are boxes of items.
I want to have a master ContainerUnit that is treated as the highest level parent of all Unit types. But that would make its Parent property null. Meaning, I want to say "who's your daddy?" to anything, without being aware of its position in the hier开发者_运维知识库archy, but then if I ask (say, in a loop) who the master container's parent is, it gets handled gracefully.
I'm looking for approaches that others have taken to solve this. I did search for this, I just didn't have much luck with my queries.
Having the outermost "universe" container return null for its container is the traditional thing to do. This has the advantage that it is easy. It has the disadvantage that you don't know that you've gone past the edge of the universe until it is too late to get back. As you said in a comment: using "null" as a flag is weak.
Two other solutions that I've seen employed are:
1) The universe object is its own container. This has the advantage that nothing is null; it has the disadvantage that it is easy to go into an infinite loop when walking the container chain, and it is unintuitive; the universe does not actually contain itself. Basically you're using equality as a flag instead of nullability as a flag; this seems weak too.
2) The universe object throws an exception when you ask for the container. This forces the caller to, instead of checking for null container, check instead for "are you the entire universe?" before asking for the container. That is, stop when you get to the top, instead of stopping when you get beyond the top. This is actually a kind of nice solution because it forces people to write defensive code. You can't just ask for a container unless you know there is one. Of course, it requires that the caller be somehow able to identify the universe object without inspecting its parent. You need an "Am I the entire universe?" method, or a well-known singleton object to compare against, or some other mechanism for identifying which is the topmost container.
A third approach is to deny the premise of the question; is it possible to construct your data type so that the container need not be known, or such that the importance of knowing it is minimized?
For example, in the compiler of course we have lots of "container" chains to walk, and we signal the global namespace by having its containing symbol be null (and by it being a well-known singleton object.) But a lot of the time we don't need to ever check for whether the parent is null because instead I write code that builds an abstraction on top of it:
static IEnumerable<Container> AllContainers(this Thing thing)
{
if (thing == null) yield break;
Container current = thing.Container;
while(current != null)
{
yield return current;
current = current.Container;
}
}
Great. Now that I have that helper method, I don't ever need to check the Container property of a thing. If I want to know, "is there any container of this thing that contains this other thing?" then I can say:
var query = from container in oneThing.AllContainers()
where container.Contains(otherThing)
select container;
bool result = query.Any();
Use the power of LINQ to move mechanistic implementation details like "how do I determine when I'm at the top?" into higher-level helper methods. Then write your logic in at the "business" level, not at the "mechanism" level.
The best way I can think of is just to handle your null case. That is, when you're looking at a ContainerUnit
and trying to get its Parent
you just add a check for null.
And example might be:
//Get all the Master Units from batch XYZ
var masterUnits = batches.Where(b => b.BatchId = XYZ)
.Single().Units.Where(u=> u.Parent == null);
Another example
//Get only units which have a parent [batch is an already initialized variable]
var childUnits = batch.Units.Where(u=> u.Parent != null);
I think having the Parent
property return null
when it is the highest object in the hierarchy makes the most sense and is the most graceful solution.
精彩评论