Many to Many Collections

G

Gary Cohan

Lets say you have two classes,

Product
and
Category.

In my e-commerce application, each category can have multiple products, and
each product and be on multiple categories.

So obviously, each Product has a collection of Category objects, and each
Category has a collection of Product objects.

My question is, if you wanted to add a product to a category and thus have a
new category <--> product mapping, would you expect to add the product to
the category, and then add the category to the product as two seperate
method calls, or is there some commonly used collection idiom that takes
care of this ?
 
S

Shripathi Kamath

Gary Cohan said:
Lets say you have two classes,

Product
and
Category.

In my e-commerce application, each category can have multiple products, and
each product and be on multiple categories.

So obviously, each Product has a collection of Category objects, and each
Category has a collection of Product objects.

My question is, if you wanted to add a product to a category and thus have a
new category <--> product mapping, would you expect to add the product to
the category, and then add the category to the product as two seperate
method calls, or is there some commonly used collection idiom that takes
care of this ?

You make the association at the same time, such that the product belonging
to the category, and the category containing the product takes place without
intervening races.

Whether you do in one method or 10, the case you have to watch for (other
cases may be possible) are that no external entity ever sees a product
without a category, and vice-versa. Of course, I am assuming that a product
has to belong to a category.

HTH,
 
X

xarax

Shripathi Kamath said:
You make the association at the same time, such that the product belonging
to the category, and the category containing the product takes place without
intervening races.

Whether you do in one method or 10, the case you have to watch for (other
cases may be possible) are that no external entity ever sees a product
without a category, and vice-versa. Of course, I am assuming that a product
has to belong to a category.

I would imagine that it is legal for a Category to have
no products, but not legal for a Product to have no categories.
A product can be discontinued and removed from a Category,
leaving the Category empty. OTOH, a Product cannot be
introduced into the Inventory without an association to
at least one Category. This can be enforced by a constructor
or initializer block.

I suggest an initializer block in a Product to add itself to
all appropriate categories. You probably want singleton
instances of distinct subclasses of Product. This is similar
to the Enum class thing that will be introduced in JDK 1.5.
Also use distinct singleton subclasses of Category.

Use a distinct Set (maybe TreeSet) object as a static field
in each base class for Product and Category. The Set object in
Category is the set of all products that intersect that category.
The Set object in Product is the set of all categories that
intersect with that product.

If you are defining products and categories at run-time,
rather than compile-time inheritance, then you'll need to
devise a similar strategy using run-time collections and
probably loading/saving the definitions as XML files.

Good luck.
 
A

Adam Maass

Gary Cohan said:
Lets say you have two classes,

Product
and
Category.

In my e-commerce application, each category can have multiple products, and
each product and be on multiple categories.

So obviously, each Product has a collection of Category objects, and each
Category has a collection of Product objects.

My question is, if you wanted to add a product to a category and thus have a
new category <--> product mapping, would you expect to add the product to
the category, and then add the category to the product as two seperate
method calls, or is there some commonly used collection idiom that takes
care of this ?


Long ago, I decided that the general case for add* methods in a many-to-many
should handle both sides of the relationship, simply because the intervening
partial states can get really confusing.

So:

class Product{
Set categories...

void addCategory(Category c){
categories.add(c);
c.products.add(this);
}
}

class Category{
Set products...

void addProduct(Product p){
products.add(p);
p.categories.add(this);
}
}



It's not exactly ideal because of the direct access to data members, but it
works.

Correctly synchronizing the methods in this case takes some work, since both
the Category and the Product should be locked before any action happens, and
the locks should be taken in the same order (regardless of whether it's the
Category or the Product that handles the association) to avoid deadlock.


-- Adam Maass
 
C

Chris Riesbeck

Gary Cohan said:
Lets say you have two classes,

Product
and
Category.

In my e-commerce application, each category can have multiple products, and
each product and be on multiple categories.

So obviously, each Product has a collection of Category objects, and each
Category has a collection of Product objects.

My question is, if you wanted to add a product to a category and thus have a
new category <--> product mapping, would you expect to add the product to
the category, and then add the category to the product as two seperate
method calls, or is there some commonly used collection idiom that takes
care of this ?

As an alternative to the other suggestions, you might also consider
have both Product and Category contain a shared bidirectional map of
products<->categories. This map would be created and stored in advance
in Product and Category objects. This localizes the bidirectional
mapping to one class.

Example, written to run in one file:


import java.util.*;

public class TestBiMap {

public static void main(String args[]) {
BiMap categories = new BiMap();
Product wrench = new Product("wrench", categories);
Product book = new Product("book", categories);
Category tool = new Category("tool", categories);
Category discounted = new Category("discounted", categories);

wrench.addCategory(tool);
wrench.addCategory(discounted);

book.addCategory(discounted);

System.out.println("Wrench categories: " +
wrench.getCategories());
System.out.println("Discounted products: " +
discounted.getProducts());
}
}

class BiMap {
Map values = new HashMap();
Map keys = new HashMap();

void add(Object key, Object value) {
collect(key, value, values);
collect(value, key, keys);
}

Collection getValues(Object key) {
return (Collection) values.get(key);
}

Collection getKeys(Object value) {
return (Collection) keys.get(value);
}

private void collect(Object key, Object value, Map map) {
Collection collection = (Collection) map.get(key);
if (collection == null) {
collection = new ArrayList();
map.put(key, collection);
}
collection.add(value);
}
}

class Product {

String myName;
BiMap myMap;

public Product(String name, BiMap map) {
myName = name;
myMap = map;
}

public void addCategory(Category category) {
myMap.add(this, category);
}

public Collection getCategories() {
return myMap.getValues(this);
}

public String toString() {
return myName;
}
}


class Category {

String myName;
BiMap myMap;

public Category(String name, BiMap map) {
myName = name;
myMap = map;
}

public void addProduct(Product product) {
myMap.add(product, this);
}

public Collection getProducts() {
return myMap.getKeys(this);
}

public String toString() {
return myName;
}
}
 

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

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,013
Latest member
KatriceSwa

Latest Threads

Top