Design gone wrong?

Design gone wrong?

Old forum URL: forums.lhotka.net/forums/t/4114.aspx


ajj3085 posted on Thursday, January 03, 2008

Hi,

I have a new requirement which is proving rather difficult. 

I have a quoting system, so of course there are line items.  It's not a flat list, because certain line items may contain other line items. Here's what I have, and why.

SimpleLineItem - a line item which has behaviors for a simple product.  You can change quantity and some users may change the unit price. 
NoteLineItem - a line item which has every property "empty," except description which any user may change.
SubTotalLineItem, RunningSubTotalLineItem - sums the unit price, tax, ext. price of items above it.  SubTotal stops when it encounters another sub total or the "top" of the document, running only stops at the top.
DiscountLineItem - computes its unit price, tax, ext. price based on the line immediately above it.
BankingFeeLineItem - a fixed fee line item
PackingFeeLineItem - a calculated fee line item, which doesn't include other fees in its calculation.
CustomLineItem - allows editing of part number, description, taxable and unit price.
BundleLineItem - we have product bundles; this item is always tied to a product which is a bundle and contains BundleSubLineItems.  These sub items are not modifiable at all.  The sub items must be present so that internally we can see them but externally customers cannot.
GroupLineItem - functions as a kind of custom bundle.  This is created by selecting one or more Simple or Custom line items and selecting group.  The price of the group is calculated by adding the prices of the items in the group.  It's possible to set a quantity for the group item or its sub items, all which affect price.  GroupLineItem has an Items colleciton of GroupSubItem, which has an Item property which exposes the item contained in the sub item.  The sub item functions like a decorator.
LineItem - an internal abstract class from which all other items inherit to get common behaviors.

All non-sub items may be moved up or down the document (in addition to cut/copy and pasted).  Items in a group can't be reordered.
IContainerLineItem - interface implemented by both Group and Bundle item, provides a common Items property.

LineItemList - internal list which is how everything is stored in memory.  The UI doesn't see this.  If there's a bundle item (with seven sub items), discount and two simple, there will be four items here.
LineItems - the public list which the UI manipulates.  It's a view which maps actions back to the LineItemList.  There would be 11 items here, because this view flattens the hierarchy.

Everything works and fits together nicely... until the users want to be able to put bundle items into groups.  Opps... everything was geared to at most one level deep, now in one particular case everything falls apart.

I actually have it working mostly, although there is a warning flag going off.  The LineItems class now does two checks; as it flattens the list, it attempts to cast the current item to IContainerLineItem (which is implemented by both Bundle and Group line items).  Before, that's all that was needed.  Now I'm also attempting to cast to the concrete GroupSubItem.  If that works, I cast the GroupSubItem.Item to IContainerLineItem to get it's Items properties. 

So that feels a bit off.  The real wall I'm hitting though is getting the internal order form printed.

Say you have this:

5 - Group 1
    2 - Bundle 1
       2 - Simple 1
       3 - Simple 2
10 - Simple 1
15 - Simple 2

It should show up on the order form like this:

30 - Simple 1
45 - Simple 2

I have a way to get the bundle sub item to take its parent's quantity into account, but I'm not sure how to get the bundle to also multple by it's containing group line item.

Any suggestions?  Did I mess up the design orginally, or is this a case of the use case changing causing large changes all around?  Anyone have a cleaner design?

tmg4340 replied on Friday, January 04, 2008

I wouldn't say it's a messed-up design.  I think it may be a little overly complex in some areas.

These part-whole hierarchies are always a bit of a tough nut.  I've never been a fan of an explosion of leaf classes, which is kinda what you have here.  I will readily concede that a use-case-driven design could lead to this, and there's no real good alternative.  You're either checking against a ton of different types, or you're checking against some kind of type property.  Which is better?  OO design will say different types - but OO design will also say that you should include methods in the base class so that you can treat the subtypes the same.

If you look at the GoF Composite pattern - which is what most people would probably say you should be using - the issues with the pattern center around what you're dealing with.  If you include a child collection at the base level, then you have to define behaviors for types that, by definition, will never have any children.  You also potentially incur an object graph overhead, since said child collection will exist at every level, whether you need it or not.

(OK - enough of this theory crap.  Let's get to some real advice, huh?  Smile [:)])

I don't see a need for GroupSubItem, since all it seems to do is expose a property to the actual object you care about.  Since the items are, by this definition, still editable within this context, GroupSubItem seems to be getting in the way.

One possible simplification I see is to take your collection-based line items and combine them into one class.  Realizing that BundleLineItem and GroupLineItem aren't the same thing, when you boil it down, they aren't all that different.  BundleLineItems contain non-editable (and, in some cases, non-viewable) children; GroupLineItems don't have that restriction.  Presumably, pricing is basically the same - it's the sum of the individual parts.  Especially if you get rid of the GroupSubItem, you might be able to combine both of those.

In the end, I think you're going to have to get to some kind of Composite hierarchy in order to protect yourself.  You designed for one level of nesting, and now they want two.  How long before they want three, four, etc.?  Also, I think you may want to step back from the individual line-item use cases to do some consolidation.  I see line items that look to have been created to fulfill mostly UI-related functions.  I realize that putting that kind of logic in the UI isn't as helpful, but the alternative is what you have here.

The one crucial thing (crucial to my way of thinking, at least) is that order is important to you.  Since you have line items that function on the previous one, or previous ones, in the list, the order of items must be preserved.  That would include in the database.  So my thoughts were centered around this.  It's an odd implementation of a Composite pattern, but it might make your life easier - and you've already started at least some of it.

Your LineItems collection flattens your hierarchy.  What if that was your one (and only) collection?  Since order is important, you'd need to introduce two concepts: depth and sequence.  Sequence is contained by depth, and depth is relatively self-explanatory.  Then you only have one collection - the collection of LineItems off your quote.  You'd want to build routines that allow a lineitem to find their next sibling, previous sibling, parent, etc.  But then your grouping/bundling could become easier, and you automatically support any level of nesting you need without generating some sort of complex object graph.  Grouping items merely requires a bit of re-ordering and some depth/sequence updates.  It removes the "what do I do with child collections in leaf objects" question.  You can still store parent-object references - they just point to an object higher up in the list.  That way, parent-level properties can easily be retrieved (for something like the bundle children quantity multiplication).

I realize this probably ends up looking a lot like your database layout, and I'm not advocating a data-driven design.  You can still have your different line items.  But you'd also want to push as much functionality into the base LineItem class as you can.  That way, your flattened collection can treat line items the same.

I also realize that, if you push your functionality into the base LineItem, you could still do a standard object graph.  Using recursive routines, that's still not too hard to process.  But, from your description, it feels like the flattened hierarchy is the more dominant form.  Add in the need to "promote" child objects to the parent level for purposes of combining/rolling up items, and I think it's an idea at least worth investigating.

I also realize this is probably a fairly radical departure from your current design.  If it were me, I'd blame it on the new use case.  Wink [;)]

ajj3085 replied on Monday, January 07, 2008

tmg4340:
I wouldn't say it's a messed-up design.  I think it may be a little overly complex in some areas.


Perhaps.  My goal was as simple as possible.  But I'm nto sure if I've achieved that or not. Smile [:)]

tmg4340:
These part-whole hierarchies are always a bit of a tough nut.  I've never been a fan of an explosion of leaf classes, which is kinda what you have here.  I will readily concede that a use-case-driven design could lead to this, and there's no real good alternative.  You're either checking against a ton of different types, or you're checking against some kind of type property.  Which is better?  OO design will say different types - but OO design will also say that you should include methods in the base class so that you can treat the subtypes the same.

Well, all those classes exist because they do have different responsiblities.  Discounts aren't like note line items, etc.

tmg4340:
If you look at the GoF Composite pattern - which is what most people would probably say you should be using - the issues with the pattern center around what you're dealing with.  If you include a child collection at the base level, then you have to define behaviors for types that, by definition, will never have any children.  You also potentially incur an object graph overhead, since said child collection will exist at every level, whether you need it or not.

I orginally did think about that pattern, but didn't go there for the reasons you specify.

tmg4340:
I don't see a need for GroupSubItem, since all it seems to do is expose a property to the actual object you care about.  Since the items are, by this definition, still editable within this context, GroupSubItem seems to be getting in the way.

Hmm, an interesting concept.  I'll have to think about this.  The GroupSubItem does a bit more than expose the Item property.  For example, a SimpleLineItem overrides IsGroupable, IsUngroupable and IsGrouped to return true, false and false.  While GroupSubItem delegates some thing, like Description, to the decorated instance, it doesn't delegate those three properties and always returns a fixed value for each of the Isxxx properties above.  But this still may be a good option.

tmg4340:
One possible simplification I see is to take your collection-based line items and combine them into one class.  Realizing that BundleLineItem and GroupLineItem aren't the same thing, when you boil it down, they aren't all that different.  BundleLineItems contain non-editable (and, in some cases, non-viewable) children; GroupLineItems don't have that restriction.  Presumably, pricing is basically the same - it's the sum of the individual parts.  Especially if you get rid of the GroupSubItem, you might be able to combine both of those.

Yes, they are similar, but the container itself acts a bit differently.  For example, only the quantity of the bundle item is changable, and the unit price by certain users.  For the group, everything is changable; the part number, description, etc.  Taxable is forced blank (also unlike the bundle).  The group item will also remove itself once it has no children.  But perhaps this is something I can again work towards.

Also, bundle pricing may be the sum of the parts, or it may be fixed.  That option is set by the product administrator when working with the bundle, and of course they can change to either method at any time.

tmg4340:
In the end, I think you're going to have to get to some kind of Composite hierarchy in order to protect yourself.  You designed for one level of nesting, and now they want two.  How long before they want three, four, etc.?  Also, I think you may want to step back from the individual line-item use cases to do some consolidation.  I see line items that look to have been created to fulfill mostly UI-related functions.  I realize that putting that kind of logic in the UI isn't as helpful, but the alternative is what you have here.

Well, I'm pretty certain that they will never ask for more than two levels of grouping.  The purpose of the group item is to provide a way to "hide" what the customer is actually getting.  Either to get around some IT policy at the customer site or because the customer just wants one line item on the invoice, for whatever reason.  The bundle's purpose isn't necessarly to hide the items to make up the bundle, its a standard "we'll sell a complete system instead of you buying all the individual parts."  Kind of like a home theater system.  You could be each piece individually, but they'll sell you a working system.  The result ends up being the same; the customer only sees the zero level items.  Internally, we only show our parts department the "deepest" items.

tmg4340:
The one crucial thing (crucial to my way of thinking, at least) is that order is important to you.  Since you have line items that function on the previous one, or previous ones, in the list, the order of items must be preserved.  That would include in the database.  So my thoughts were centered around this.  It's an odd implementation of a Composite pattern, but it might make your life easier - and you've already started at least some of it.

Yes, the ordering piece is in place currently.  The items in a List stay where you place them, and you can ask a LineItem to move up or down.  When saving, a position is deteremined.

tmg4340:
Your LineItems collection flattens your hierarchy.  What if that was your one (and only) collection?  Since order is important, you'd need to introduce two concepts: depth and sequence.  Sequence is contained by depth, and depth is relatively self-explanatory.  Then you only have one collection - the collection of LineItems off your quote.  You'd want to build routines that allow a lineitem to find their next sibling, previous sibling, parent, etc.  But then your grouping/bundling could become easier, and you automatically support any level of nesting you need without generating some sort of complex object graph.  Grouping items merely requires a bit of re-ordering and some depth/sequence updates.  It removes the "what do I do with child collections in leaf objects" question.  You can still store parent-object references - they just point to an object higher up in the list.  That way, parent-level properties can easily be retrieved (for something like the bundle children quantity multiplication).

Yes, I have thought of this, and Rocky has also suggested it.  My problem has been to reliably keep the items in order, or how to determine if an item can be moved.  For example, bundle sub items cannot be re-ordered, they must appear in the order the product administrator set.  If the bundle item needs to move up, so do all its children.  Maybe this is simplier than I'm imagining though.

tmg4340:
I realize this probably ends up looking a lot like your database layout, and I'm not advocating a data-driven design.  You can still have your different line items.  But you'd also want to push as much functionality into the base LineItem class as you can.  That way, your flattened collection can treat line items the same.

No, not at all.  Its confusing, because at the same time items ARE flat, but also exhibit behaviors which also suggest a hierarchy.

tmg4340:
I also realize that, if you push your functionality into the base LineItem, you could still do a standard object graph.  Using recursive routines, that's still not too hard to process.  But, from your description, it feels like the flattened hierarchy is the more dominant form.  Add in the need to "promote" child objects to the parent level for purposes of combining/rolling up items, and I think it's an idea at least worth investigating.

Yes, I agree that flat is more dominant and that I may have a lot of work to do... that's why I posted here.  Since it's just me doing everything for this application (database to deployment and support), I need some place to bounce thoughts around.. and this forum has been a great place.  Part of my dilemma is that I want to get this done rather soon, but I also don't want a bad design.  I'm while I'm almost certain they'll never ask to group anything else (rather, ask to have one group of items under another group), I'm not 100% sure. 

tmg4340:
I also realize this is probably a fairly radical departure from your current design.  If it were me, I'd blame it on the new use case.  Wink [;)]

Yes, I knew that might be a possiblity when I ask for help.  I certainly have a lot to think about.

In the interest of "getting it done" and the likelihood that I won't be asked to allow for groups of groups (just groups of products or product bundles), I think I may try for eliminating GroupSubItem and making items which can be grouped a bit smarter about when they are grouped (i.e., if their container is actually a line item or some other container).

Of course, if there are any other suggestions, I'd still love to hear them!

Thanks
Andy

tmg4340 replied on Monday, January 07, 2008

ajj3085:
tmg4340:
These part-whole hierarchies are always a bit of a tough nut.  I've never been a fan of an explosion of leaf classes, which is kinda what you have here.  I will readily concede that a use-case-driven design could lead to this, and there's no real good alternative.  You're either checking against a ton of different types, or you're checking against some kind of type property.  Which is better?  OO design will say different types - but OO design will also say that you should include methods in the base class so that you can treat the subtypes the same.

Well, all those classes exist because they do have different responsiblities.  Discounts aren't like note line items, etc.

Sure - I'm not saying all the LineItem classes are the same.  But they all do the same basic things, so perhaps your base class could turn into something more like a Template class.  I don't know the full extent of your app, but if you could do something like that, you may be able to not care about the specific subtypes more.  That makes the code a bit cleaner.

ajj3085:
tmg4340:
One possible simplification I see is to take your collection-based line items and combine them into one class.  Realizing that BundleLineItem and GroupLineItem aren't the same thing, when you boil it down, they aren't all that different.  BundleLineItems contain non-editable (and, in some cases, non-viewable) children; GroupLineItems don't have that restriction.  Presumably, pricing is basically the same - it's the sum of the individual parts.  Especially if you get rid of the GroupSubItem, you might be able to combine both of those.

Yes, they are similar, but the container itself acts a bit differently.  For example, only the quantity of the bundle item is changable, and the unit price by certain users.  For the group, everything is changable; the part number, description, etc.  Taxable is forced blank (also unlike the bundle).  The group item will also remove itself once it has no children.  But perhaps this is something I can again work towards.

Also, bundle pricing may be the sum of the parts, or it may be fixed.  That option is set by the product administrator when working with the bundle, and of course they can change to either method at any time.

I know what this is going to sound like, but if you took advantage of the fact that the line items know who their parent is, you could rely on the parent type to manage some of that.  I know that sounds like you're pushing some of the responsibility of the LineItem to another class, but since the operations are dependent on the parent anyway, there's some coupling built in.

At least that's what I tell myself to feel better about it.  Smile [:)]

ajj3085:
tmg4340:
Your LineItems collection flattens your hierarchy.  What if that was your one (and only) collection?  Since order is important, you'd need to introduce two concepts: depth and sequence.  Sequence is contained by depth, and depth is relatively self-explanatory.  Then you only have one collection - the collection of LineItems off your quote.  You'd want to build routines that allow a lineitem to find their next sibling, previous sibling, parent, etc.  But then your grouping/bundling could become easier, and you automatically support any level of nesting you need without generating some sort of complex object graph.  Grouping items merely requires a bit of re-ordering and some depth/sequence updates.  It removes the "what do I do with child collections in leaf objects" question.  You can still store parent-object references - they just point to an object higher up in the list.  That way, parent-level properties can easily be retrieved (for something like the bundle children quantity multiplication).

Yes, I have thought of this, and Rocky has also suggested it.  My problem has been to reliably keep the items in order, or how to determine if an item can be moved.  For example, bundle sub items cannot be re-ordered, they must appear in the order the product administrator set.  If the bundle item needs to move up, so do all its children.  Maybe this is simplier than I'm imagining though.

If you introduce depth and sequence, it's not so bad.  Since the child items point to their parent, and sequence is encapsulated by parent item, moving a parent automatically moves the children - they still point to their parent, and their ordering doesn't change.  Moving is pretty simple too - just change the sequence numbers of the moving items.

Maybe an example.  Say you have something like this:

Parent ID    Child ID    Depth    Sequence
---------    --------    -----    --------
1            <NULL>      1        1
1            2           2        1
1            3           2        2
1            4           2        3
5            <NULL>      1        2
5            6           2        1
5            7           2        2

If you need to move 5 above 1, then you just change their Sequence values.  Nothing else changes, and the children stay where they should be.

As for determining what can or can't be moved, I'd fall back on "operations based on parent type" again, since the type of parent determines what can be done to its child(ren).

ajj3085:
tmg4340:
I also realize that, if you push your functionality into the base LineItem, you could still do a standard object graph.  Using recursive routines, that's still not too hard to process.  But, from your description, it feels like the flattened hierarchy is the more dominant form.  Add in the need to "promote" child objects to the parent level for purposes of combining/rolling up items, and I think it's an idea at least worth investigating.

Yes, I agree that flat is more dominant and that I may have a lot of work to do... that's why I posted here.  Since it's just me doing everything for this application (database to deployment and support), I need some place to bounce thoughts around.. and this forum has been a great place.  Part of my dilemma is that I want to get this done rather soon, but I also don't want a bad design.  I'm while I'm almost certain they'll never ask to group anything else (rather, ask to have one group of items under another group), I'm not 100% sure.

Wherever you can get the help... hopefully I am helping!

I work under a similar type of situation, and here's how I look at it.  I would love to have the time to develop The Perfect Design, and I often get my brain in a twist trying to get there.  But in the end, if the design handles the needs of the app, then it's a good design.  Everyone can see the holes they coded, given enough time.  It's hard to plan for all the possible future cases, and in most cases you don't need to.  You already know this.  If what you have can be modified to accept the new conditions without a ton of work, and you're comfortable with the changes, do it and move on.  You seem like a smart-enough developer that you're not going to code yourself into a corner that requires you to blow up the whole thing.  Just because I think the design might be overly complicated certainly doesn't mean it is - I know nothing about the app or your customers.  The only thing I'll say is that, if it were me, I wouldn't code myself into the "they aren't going to ask for more nesting levels" situation.  But I only say that because I've been burned on that more than once.  I know that makes the problem harder to fix than what's in front of you, and if they really never do ask for it, then you wasted your time.

ajj3085 replied on Tuesday, January 08, 2008

tmg4340:
Sure - I'm not saying all the LineItem classes are the same.  But they all do the same basic things, so perhaps your base class could turn into something more like a Template class.  I don't know the full extent of your app, but if you could do something like that, you may be able to not care about the specific subtypes more.  That makes the code a bit cleaner.


Yes, they do.  I think I'm not understanding something here, because I do have a base LineItem class which does implement common behaviors (like MoveUp, MoveDown, etc.).  Is there something else you're suggesting?

tmg4340:
I know what this is going to sound like, but if you took advantage of the fact that the line items know who their parent is, you could rely on the parent type to manage some of that.  I know that sounds like you're pushing some of the responsibility of the LineItem to another class, but since the operations are dependent on the parent anyway, there's some coupling built in.

I think that's kind of the approach I'm taking.  I don't want BundleLineItem to depend on GroupLineItem in any way, but I'm putting code in BundleLineItem to be able to recognize if it is a zero depth item or not.  I'm also doing the same for SimpleLineItem (which, thinking about it, I should probably rename to ProductLineItem).  I'm trying to use the interfaces I have to do this.  I have a bunch.. ILineItemContainer (a collection class which holds line items), IContainerLineItem (a line item which containers other line items), IGroupableLineItem, IGroupedSubItem, ILineItemInternal.  Then there's ILineItem, which is ALL the UI sees.  Maybe it is time to see if I can't simplify a bit.

tmg4340:
If you introduce depth and sequence, it's not so bad.  Since the child items point to their parent, and sequence is encapsulated by parent item, moving a parent automatically moves the children - they still point to their parent, and their ordering doesn't change.  Moving is pretty simple too - just change the sequence numbers of the moving items.

Maybe an example.  Say you have something like this:

Parent ID  Child ID   Depth  Sequence
---------    --------    -----    --------
1            <NULL>     1        1
1            2               2        1
1            3               2        2
1            4               2        3
5            <NULL>     1        2
5            6               2        1
5            7               2        2

If you need to move 5 above 1, then you just change their Sequence values.  Nothing else changes, and the children stay where they should be.

That (except for depth) is what I first tried.  It got complex, line items needed to know other line item's sequence numbers, etc.  I realized that List kept the order, so I could remove that complexity and just move things around in the list.  I could probably keep numbers in sync though.. my problem was exposing a BusinessListBase class that enforced this order.  In my case, ordering is not just a UI feature, it's a business feature.  But maybe I wasn't going about it the right way.

tmg4340:
As for determining what can or can't be moved, I'd fall back on "operations based on parent type" again, since the type of parent determines what can be done to its child(ren).

Well, right now the only items that can't be moved are those contained in a BundleLineItem.  Technically, items within a Group could be moved by the user... the current coding doesn't allow this, but it's not actually a business requirement, I think it was just a shortcut I took... or something I didn't think about.  Indeed, this may be useful to users, because if they want a certain order within the group they need to ungroup the whole thing, re-order, regroup. 

tmg4340:
Wherever you can get the help... hopefully I am helping!

You have been very helpful.  Going back and forth like this has given me some ideas.

tmg4340:
I work under a similar type of situation, and here's how I look at it.  I would love to have the time to develop The Perfect Design, and I often get my brain in a twist trying to get there.  But in the end, if the design handles the needs of the app, then it's a good design.  Everyone can see the holes they coded, given enough time.  It's hard to plan for all the possible future cases, and in most cases you don't need to.  You already know this.  If what you have can be modified to accept the new conditions without a ton of work, and you're comfortable with the changes, do it and move on.

Well, that's where I'm at right now.  This one feature is harder to implement in the current design than I would have liked.  I guess that's just how things go sometimes.  I don't mind doing alot of work again now, I just would like to have a design so that later changes don't require so much.  But I guess that's not always possible, since we can't see the future. 

tmg4340:
You seem like a smart-enough developer that you're not going to code yourself into a corner that requires you to blow up the whole thing.  Just because I think the design might be overly complicated certainly doesn't mean it is - I know nothing about the app or your customers.  The only thing I'll say is that, if it were me, I wouldn't code myself into the "they aren't going to ask for more nesting levels" situation.  But I only say that because I've been burned on that more than once.  I know that makes the problem harder to fix than what's in front of you, and if they really never do ask for it, then you wasted your time.

Well, I might be at a point now where my current design needs to be blown up.  Smile [:)]  I don't want to code myself into a corner either, but at the same time I try to stick to the YAGNI principle, so that I can get things done in a reasonable amount of time.  Although my employer has been great about saying "take as much time as you need to do it," I still like to get things out the door sooner rather than later.  Especially since much of what I'm replacing is a huge mess, and they've been very happy with what I've been able to deliver thus far.

I think I'll stay the current route, and follow your suggestion to kill GroupSubItem.  It seems to be going much better so far, although there are some kinks to work out still... mostly that I haven't finished coding this concept yet.  Smile [:)]  If the time comes that I need more nesting, my current design shouldn't prevent me from moving to a full Composite pattern.. it won't be any harder than it is now, and it really wouldn't be that hard... just more difficult than killing GroupSubItem.

Thanks very much for your help, it has been useful!

Andy

ajj3085 replied on Tuesday, January 08, 2008

Ok, coding things has been.. difficult.  So I think I'll go the composite pattern route.  I do have some things I'm still not sure of though.  Currently, users can't group Discount, Note, and any kind of Fee line items.  They must always be 'top level,' contained by the LineItemContainer my new line item class super class. 

Currently, I have a flag, IsGroupable defined on every line item, and the UI checks this value when the context menu is displayed, and if any selected item returns false, it grays out the Group Items menu option.  I'd like to continue to do this somehow, but I'm at a loss.

The design I'm thinking of has LineItemContainer as the superclass.  Then I have LineItemLeaf, which is what most things will sub-class.. Notes, Discounts, Fee, Product (formally SimpleLineItem), BundleComponentLineItem (formally BundleSubLineItem).  These will not support the adding or removing of children line items. 

I'll also have LineItemNode, which will actually be able to hold other line items.  From this, BundleLineItem and GroupLineItem will inherit.  Also, LineItems will inherit from this as well.  LineItems will always be the root node in the tree. 

Back to the problem.. how do I give the UI clues as to what items can be regrouped?  I thought about having a CanAddItem and CanRemoveItem... but the problem is that a Discount CAN be removed from LineItems, but not added to GroupLineItem.  Hmm... maybe that's the trick... but the problem is that a group item isn't created until after the user selects the Group Items menu command. 

Thoughts?

tmg4340 replied on Wednesday, January 09, 2008

Well - the first thing that comes to mind is a static list of allowable child types for your GroupLineItem class.  This isn't something I see you being able to put in the LineItemNode base class, since the check is really type-specific, and you can't have virtual or abstract static members.  Plus, the check isn't really useful for the other list types.  BundleLineItem objects only accept BundleComponentLineItem children, but that's completely enforced in the UI.  And LineItems objects accept all types.

Anyway, you add a static constructor for your GroupLineItem class that populates this list.  Finally, you put a "CanContainType" method that returns True or False based on a simple Contains() check.  Then, in your context-menu code, you call "GroupLineItem.CanContainType(Type)" for each selected item, and enable/disable your menu item appropriately.  And maybe you also have an overload that takes a list of items and does the looping for you - depends on how often you have to check multiple types.

It's not polymorphic, but since the grouping functionality is specific to the GroupLineItem type, that's not so bad.  At least it isn't to me.  It also "feels" better - knowing whether a specific line item can be grouped feels like GroupLineItem functionality to me.

- Scott

ajj3085 replied on Wednesday, January 09, 2008

Ya, I come up with something similar, although you raise a good point I missed.  I have a IsReparentable, which indicates if you can change the parent of a lineitemcontainer.  I also have a Reparent method which asks the line item to update it's parent.  It will remove itself from it's current parent after it adds itself to the requested parent.

I thought this would be good enough, but your post remidned me.. the UI creates any line item via a LineItemFactory.  So techinically it could create a NoteLineItem and then call Add on a GroupLineItem.  So I'll need to check there anyway.  I think though instead of checking the type, I can check the items IsReparentable property and throw an exception if that returns false.

Argh... I also thought of something else.  LineItemContainer is a BusinessBase object.  How do I know when to delete line items that are totally out of the graph?  Hmm..

Copyright (c) Marimer LLC