Hairy object tree problem

G

Gustaf

I'm working on an object model that will represent trees of products, where one product may contain others (components of a product). Each product needs to keep track of its parent and child products. That far it seems I'll only need one class:

public class Product
{
private string name;
private Product parent;
private List<Product> children;

public Product(string name)
{
this.name = name;
this.parent = null;
this.children = new List<Product>();
}

public string Name
{
get { return this.name; }
}

public void AddChild(Product product)
{
product.parent = this;
this.children.Add(product);
}

public Product Parent
{
get { return this.parent; }
set { this.parent = value; }
}

public List<Product> Children
{
get { return this.children; }
}

public void Traverse()
{
Console.WriteLine(this.name);
foreach (Product product in this.children)
{
product.Traverse();
}
}
}

But there's another requirement: there may be several variants of each product. So in addition to having children, any product in the tree is potentially a collection of alternative products! This requirement seem to complicate the model a great deal. When looking for a solution, I found the Composite design pattern:

http://en.wikipedia.org/wiki/Composite_pattern

If I understand it right, I'd need three classes:

public abstract class ComponentProduct
public class Product : ComponentProduct
public class CompositeProduct : ComponentProduct

I'd appreciate comments on whether this is a good solution in my case, or whether there's an obvious and simple solution that eludes me.

Also, the way I implement Children above has a serious flaw. The calling method gets access to all the members of the List<> class, which makes it possible to side-track the AddChild() method, by adding children like this:

myProduct.Children.Add(new Product(...));

How do I prevent that?

Many thanks,

Gustaf
 
P

Pavel Minaev

I'm working on an object model that will represent trees of products, where one product may contain others (components of a product). Each product needs to keep track of its parent and child products. That far it seems I'llonly need one class:

public class Product
{
    private string name;
    private Product parent;
    private List<Product> children;

    public Product(string name)
    {
        this.name = name;
        this.parent = null;
        this.children = new List<Product>();
    }

    public string Name
    {
        get { return this.name; }
    }

    public void AddChild(Product product)
    {
        product.parent = this;
        this.children.Add(product);
    }

    public Product Parent
    {
        get { return this.parent; }
        set { this.parent = value; }
    }

    public List<Product> Children
    {
        get { return this.children; }
    }

    public void Traverse()
    {
        Console.WriteLine(this.name);
        foreach (Product product in this.children)
        {
            product.Traverse();
        }
    }

}

But there's another requirement: there may be several variants of each product. So in addition to having children, any product in the tree is potentially a collection of alternative products! This requirement seem to complicate the model a great deal. When looking for a solution, I found the Composite design pattern:

I'm not sure I follow you. How does the fact that "there may be
several variants of each product" leads to "any product in the tree is
potentially a collection of alternative products"? From your original
class definition, this is not the case - each product is a product in
itself (in any case), and then it also contains a collection of other
products. I don't see what composite pattern would buy you here, since
you don't have to differentiate leaf and non-leaf nodes.

Can you elaborate on what exactly you mean by "several variants of
each product"?

Also, the way I implement Children above has a serious flaw. The calling method gets access to all the members of the List<> class, which makes it possible to side-track the AddChild() method, by adding children like this:

myProduct.Children.Add(new Product(...));

How do I prevent that?

You expose your own collection rather than List<T>. The simplest way
is to create an inner class inside Product that extends
System.ComponentModel.Collection<T>, and override InsertItem/
RemoveItem/SetItem/ClearItems to set/reset Parent as objects are added/
removed to the tree. Once you do that, you should also make Parent.set
private so that the client cannot add a product to a collection, and
then change its Parent to something else entirely (or, alternatively,
you will need to write similar logic for Parent.set - it would have to
remove the product from its parent, and then add it to the new parent
- but that's trickier, because you'll need to guard against recursion
when Parent.set calls Add which calls Parent.set ...).
 
G

Gustaf

Pavel said:
Can you elaborate on what exactly you mean by "several variants of
each product"?

Okay, variant wasn't the best word, because it implies the variant is subordinate to some default product. Let's say alternative instead.

Imagine a car product, with child products for chassis, wheels and so on. One day, the researchers come up with a whole new gear stick knob. The new knob is compatible with the old one, so now there are two alternative knobs as children to the gear stick product. They are mutually exclusive. If the whole gear stick is product A, and the stick is product B, the knob would be product C *or* D. Both B and the collection (C|D) are children of A.

Gustaf
 
P

Pavel Minaev

Imagine a car product, with child products for chassis, wheels and so on.One day, the researchers come up with a whole new gear stick knob. The newknob is compatible with the old one, so now there are two alternative knobs as children to the gear stick product. They are mutually exclusive. If the whole gear stick is product A, and the stick is product B, the knob wouldbe product C *or* D. Both B and the collection (C|D) are children of A.

It seems that your original class models this just fine - you'd have
one instance for A, containing a reference to an instane of B, and
another reference to instance of either C or D. On the other hand, if
you want to encode information about alternatives, then you can
introduce a class hierarchy for that (similar to, but not quite as,
Composite):

abstract class ProductComponent { }

class Product : ProductComponent {
List<ProductComponent> components;
}

class ProductComponentChoice : ProductComponent {
List<Product> alternatives;
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top