tags:

views:

175

answers:

7

Before the introduction to generics to the Java language I would have written classes encapsulating collections-of-collections-of-collections. For example:

class Account {
   private Map tradesByRegion; //KEY=Region, VALUE=TradeCollection
}

class TradeCollection {
   private Map tradesByInstrument; //KEY=Instrument, Value=Trade
}

Of course, with generics, I can just do:

class Account {
   private Map<Region, Map<Instrument, Trade>> trades;
}

I tend to now opt for option #2 (over a generified version of option #1) because this means I don't end up with a proliferation of classes that exist solely for the purpose of wrapping a collection. But I have a nagging feeling that this is bad design (e.g. how many nested collections should I use before declaring new classes). Opinions?

A: 

I prefer #2. It is clearer what is going on, and is typesafe at compile time (I prefer having as many things go wrong at compile time as possible, as opposed to having them happen at runtime... in general I like it when nothing goes wrong).

Edit:

Well there are two ways that I can see... I guess it depends on which you would use:

class Account
{
   private Map<Region, TradeCollection> tradesByRegion;
}

class TradeCollection
{
   private Map<Instrument, Trade> tradesByInstrument;
}

or

class Account<R extends Region, I extends Instrument, T extends Trade, C extends TradeCollection<I, T>> 
{
   private Map<R, C> tradesByRegion;
}

class TradeCollection<I extends Instrument, T extends Trade>
{
   private Map<I, T> tradesByInstrument;
}
TofuBeer
Of course; but I could make #1 typesafe by including generics. I'll edit my question
oxbow_lakes
I started to say that. but as I made it generic I wound up with essentially the same code... I'll try again and see what I get
TofuBeer
+2  A: 

I have made this simple rule for me: Never more than two <'s and two commas in a generics declaration and preferably only one comma. After that I introduce custom types. I think this is the point where the readability suffers enough to warrant additional concepts.

There is a real good reason to avoid too deep generics: The complexity is not only in the actual declaration but usually tends to be equally visible in the construction logic. So a lot of code has a tendency to become convoluted if you nest these declarations too deeply. Creating intermediate classes can help a lot. The trick is often to find the proper intermediate classes.

I definitely think you should go a slight bit back towards your old standard. Actually your second sample is the exact pain-point where I'd still accept generics-only.

krosenvold
I won't show you my generics then (I have one that covers about 10 lines to keep it readable :-)
TofuBeer
@TofuBeer Better not do that. That's write-only code in my world :)
krosenvold
Of course one problem with #1 is that you end up asking lots of questions like "what was a TradeCollection again?" - That is, you need to do much more code browsing from the Account class to understanding what the structure of the trade collection actually is
oxbow_lakes
@krosenvold I am in the process of killing it :-)
TofuBeer
+3  A: 

2 is better because:

  • Less code accomplishes the same effect (better, actually, as in #1 some of your type information exists only in comments)
  • It's completely clear what's going on.
  • Your type errors will be caught at compile time.

What is there to recommend 1? admittedly the Map< Integer , < Map < String, < Map< ... generics are a bit hard to get used to, but to my eye it's much easier to understand than code with maps, and lists of maps, and maps of lists of maps, and custom objects full of lists of maps.

Steve B.
I've edited the question to make clear that a post-generics option #1 would still be typesafe as I would parametrize the Maps
oxbow_lakes
+2  A: 

Normally there will also be some code to operate on the collections. When this becomes nontrivial I package up the collection with the behavior in a new class. The deeper the nesting is the more likely this will be the case.

starblue
+3  A: 

Combination of the two. While you can use generics to replace custom classes, you still will want to use a class to encapsulate your concepts. If you're just passing maps of maps of maps of lists to everything, who controls what you can add? who controls what you can remove?

For holding the data, generics is a great thing. But you still want methods to validate when you add a trade, or add an account, and without some kind of class wrapping your collections, nobody controls that.

John Gardner
I certainly agree: I think that the collections are an implementation detail and I don't pass them around or expose them in the public API
oxbow_lakes
+2  A: 

I think it's better to keep objects in mind and emphasize the collections a bit less. Reification is your friend.

For example, it's natural to have Student and Course objects if you're modeling a system for a school. Where should grades be captured? I'd argue that they belong in an object where Student and Course meet - a ReportCard. I wouldn't have a GradeCollection. Give it some real behavior.

duffymo
I agree: ReportCard vs GradeCollection is a very good example of good design choice in my book.
javashlook
+1  A: 

I think the answer to this is that it depends on the situation. Generally, introducing a type is useful if that also introduces methods related to the type, if those intermediate types are passed around independently, etc.

You might find this bit of advice from Rich Hickey (the creator of Clojure) about creating interoperable libraries kind of interesting:

It's intended as specific advice to Clojure library writers but I think is interesting food for thought even in Java.

Alex Miller