I am using a composite pattern as shown in the class diagram below. Basically my primary leaf (an Allocation) has a single property, Duration, that I want to accumulate in various composites.
I also want to have a count of allocations, and the ability to retu开发者_如何学运维rn the allocations as a collection I can use for data binding in presentations.
This question is about the C# implementation, specifically:
- There is an interesting post here that has an extension to do this recursively, as well as an alternative approach that avoids recursion altogether. Would you have a common method to get at the Allocations the way I am doing? I haven't decided yet if the code I wrote feels awkward because I am not comfortable with pattern, or if it is awkward!
- Do you see any easy opportunities here to cache the results of the first iteration and thereby minimize further iteration?
Cheers,
BerrylCode in the Composite class
public class AllocationComposite : AllocationNode
{
protected readonly IList<AllocationNode> _children;
public AllocationComposite() { _children = new List<AllocationNode>(); }
#region Implementation of AllocationNode
public override void Adopt(AllocationNode node) ...
public override void Adopt(IEnumerable<AllocationNode> nodes)...
public override void Orphan(AllocationNode node)...
public override void Orphan(IEnumerable<AllocationNode> nodes)...
public override IEnumerable<AllocationNode> Descendants
{
get
{
return _children.Concat(_children.SelectMany(child => child.Descendants));
}
}
public override IEnumerable<Allocation> Allocations
{
get
{
return Descendants.OfType<Allocation>();
}
}
public override TimeSpan Duration {
get {
return Allocations.Aggregate(TimeSpan.Zero, (current, child) => current + child.Duration);
}
}
public override int Count { get { return Allocations.Count(); } }
#endregion
public override string ToString() {
return String.Format("{0} for {1}",
"allocation".PluralizeWithCount(Count),
"hour".PluralizeWithCount(Duration.TotalHours, "F2"));
}
}
}
Default implementation in abstract Component
public abstract class AllocationNode: Entity, IAllocationNode
{
#region Implementation of AllocationNode
public virtual TimeSpan Duration { get { return TimeSpan.Zero; } set { } }
public virtual void Adopt(AllocationNode node) { throw new InvalidOperationException(...)); }
public virtual void Adopt(IEnumerable<AllocationNode> nodes) { throw new InvalidOperationException(...)); }
public virtual void Orphan(AllocationNode node) { throw new InvalidOperationException(...)); }
public virtual void Orphan(IEnumerable<AllocationNode> nodes) { throw new InvalidOperationException(...)); }
public virtual int Count { get { return 1; } }
public virtual IEnumerable<AllocationNode> Descendants { get { return Enumerable.Empty<AllocationNode>(); } }
public virtual IEnumerable<Allocation> Allocations { get { return Enumerable.Empty<Allocation>(); } }
#endregion
}
Do you want to have a collection of all nodes flattened (IEnumerable allNodes)? If so, after a quick look at the post you linked, I think that both solutions are OK. If the cosiderations LukeH mentions don't apply to your case, I would stick to LINQ solution because of its clarity, but it's up to you.
Caching is easy if you don't modify your structure. It gets very complicated if addition and deletions take place. Anyway, what do you want to achieve with caching? Have the descendants count cached? Descendants themselves? On which level? Root level or any level?
If you have an abstract class, it would be more elegant to make its methods abstract rather than virtual throwing exceptions or returning empty collections.
PS. The class diagram is not synchronized with the code (i.e. placement of Duration property)
精彩评论