Question

I'm building a complex tree of objects. There are a total of five types, A, B, C, D, and E. There is a single instance of A, which is the root node. A has one or more Bs as children, each B has one or more Cs as children, and so on. The original data format is a stream that consists of 5 numbers (numerical IDs for A, B, C, D, and E) plus metadata that may be associated with each one.

I'm attempting to add some level of filtering to my tree. My current approach is to take the input and pass it into the constructor for A and then flow it down to the Bs that A construct and the Cs that B construct and so on. However, the filtering ultimately happens at the level of E. If all of the Es for a given D don't exist, I don't want to create that D. If C doesn't have any Ds, I don't want that C to exist.

My initial thought is to throw an exception, but that seems like using exceptions for flow control. Although some languages favor exceptions, I'm currently working in Java, where the tendency is for exceptions to be for exceptional conditions. I don't think that not matching a filter is an exceptional case.

What are my options for ensuring that my final tree only contains nodes with children and the filter is applied?

Was it helpful?

Solution

I would do this by using a Map<Id, Container> at each level of the data structure. Write a private helper method that will return the container if it already exists, or creates a new Container if it does not yet exist. This is method is commonly called getOrAdd.

As usual, making sure that the the underlying container still exists is a bunch of boilerplate code which you are in danger of copy-pasting repeatedly if you do this wrong. Instead, you should create your own data structure to do this, or you could use Guava to help out which provides a number of useful tools including Multimap (which you can use if the data structures are pretty dumb) and ForwardingMap (which will make it easier to write your own Map implementations if you have metadata at every level). Then, just write get to use a Supplier / Factory (if not using Guava) if the container doesn't yet exist, or return the existing one if it does.

OTHER TIPS

I'll start by pointing out that building complex objects in a constructor is not always a good idea.

That said, if it's the way your code currently behaves, and you don't want to change that, you should probably be able to trade your constructors for lightweight factories.

Instead of A() doing new C() and then deciding it didn't really need it, you could do (with a factory or a static method, depending of your language): A() calling C#get() that returns a C or null if it's filtered out.

Licensed under: CC-BY-SA with attribution
scroll top