8

I have a bunch of modules. I can break these modules into different categories that are complete and do not overlap. For example, three categories, with ids that can be expressed as Animal, Vegetable, and Mineral. I further break these categories down into subcategories, which again are distinct, complete and do not overlap. For example, ids that can be expressed as Mammal, Reptile, Legume, Root, Rock, Gem. Finally, underneath these categories, there are the modules themselves, e.g. Cat, Dog, Iguana, Bean, Quartz, Emerald, etc.

Category hierarchy

Here are my common use cases:

  1. I need to call various methods on all modules.
  2. I need to get flat snapshot of the current state of all data across all modules.
  3. I need to call various methods on all modules in a particular category (but not subcategory).
  4. I need to call various methods on a particular specific module based on it's known ID.
    • This could be a "do something" or a "tell me some data about yourself"
  5. I need to store aggregate data about all modules in a particular category (but not subcategory).

How should I store this data?

Some other relevant facts:

  • The categories are established in runtime
    • As such, the bottom level modules do share a common interface.
  • Once they're set up, they don't change in that particular run - they are based on data in configuration files.

Here's what I currently do:

  1. I have a class that contains a Map<Category, CategoryDataStructure>. This class also maintains a separate Collection<Module> view of the data for use with requirement #2.
  2. CategoryDataStructure has chained delegation methods that send the method call down the chain, via SubCategoryDataStructure.
  3. CategoryDataStructure also stores the aggregate data used in requirement #5.

It works, but it's honestly quite unwieldy. The whole thing is stateful / mutable and hard to change. If I want to add new behavior I have to add it in many places. Currently the data structures themselves have a lot of business logic as well; the delegation methods. Also the parent data structure has to do a lot of business logic to create a particular module and it's parent data structure if need and that parent's data structure if needed.

I'm looking to somehow break apart data management logic away from the data structure itself, but because of the nesting it's complicated. Here are some other options I've been considering:

  1. Create a simple Map<Category, Map<Subcategory, Module>>, and put all the code to keep its state in a different class. My concern with doing this is requirements #1 and #2, it will be difficult to keep the view consistent as now I'll have two different data structures that represent the same data.
  2. Do everything in a flat data structure, and loop through the entire structure when I'm looking for a particular category or sub category.
  • Have you considered breaking up the responsibility of "figure out which objects in the hierarchy to inspect or use" by using one or more Visitors? – user22815 Oct 2 '15 at 15:34
  • @Snowman I have not considered that option; are you suggesting storing the data flat, and then when I need to call a method, send the visitor to everybody and checking whether something needs to happen in the handleVisitor class? – durron597 Oct 2 '15 at 15:41
  • It is a little more complicated than that, I just wanted to make sure before I type up a whole answer. – user22815 Oct 2 '15 at 15:42
  • Maybe you could flip the top-down structure and proceed bottom-up: thing.getType() returns "Animal" That's pretty flat. – noumenal Oct 2 '15 at 16:34
6

It appears that the core issue here is you have objects arranged in a hierarchy based on their identity, but are using them in a non-hierarchical manner.

An analogy would be storing files in directories based on their file type, but searching through each directory and only loading certain ones based on some criteria other than the type.


I'm looking to somehow break apart data management logic away from the data structure itself, but because of the nesting it's complicated. Here are some other options I've been considering:

This is a good goal, and there is an easy way to start splitting the responsibilities without a major refactor: use Visitors.

The idea is that if you need to inspect or operate on only certain elements in the hierarchy, you put that logic in the visitor itself. You can then write multiple visitors, each one operating on different elements, and performing different actions.

Each unit of logic is now self-contained to a specific visitor. This enhances the SRP-ness of your code. If you need to change how an operation is done, you do so only in the visitor that implements that logic. Your object hierarchy should stay the same, minus superficial changes to expose necessary data.

There are multiple ways to implement visitors depending on the specific goals, but the general idea is each node in the hierarchy accepts a visitor object. Its implementation looks like this, and can be stuffed into some abstract parent class:

public class Node {
  public void accept(Visitor v) {
    v.accept(this);
    for (Node child : children) {
      child.accept(v);
    }
  }
}

This ensures that no matter how you wire up your nodes at runtime, every node will be processed.

Your visitor looks like this:

public interface Visitor {
  accept(Node n);
}

The visitor's accept(Node) method is where the real work is done. You will need to inspect the node and conditionally do different things (including ignore the node).


For example, you might be able to do the following:

Node root = ...;

// Print information on the hierarchy.
root.accept(new DebugVisitor());

// Does stuff with modules, ignores subcategories.
root.accept(new FrobnicateModulesVisitor());

// Eats vegetables, ignores animals and minerals.
root.accept(new EatYourVegetableVisitor());

Each visitor is a self-contained class that contains the logic for each operation with no need to mix concerns with other visitors or the nodes.

  • quick question regarding your Note class: where is "children" defined...or was this purely an illustrative example? – user209881 Jan 6 '16 at 7:28
  • @Bey nothing is defined, that was purely illustrative. I assume readers will be familiar with graph theory and trees. – user22815 Jan 6 '16 at 15:54
1

A deep level of nesting suggests that you should refactor actions into smaller functions, which can be chained using return statements. If you need to apply the same method for multiple inputs you could take advantage of function.apply() in Java 8.

Assuming that the different items do not share any properties, you could implement an interface, which requires that a specific set of methods are implemented. For each level I would create one interface, which is then extended for each sub-node, for example: Entity, Phylum, Species. In addition you need three classes for each of the three entities.

The data can be stored as properties of the object instances. To achieve a flat snapshot I would iterate across the data using function.apply().

  • Hi, thanks for your answer. The bottom level elements do share a common interface, I thought that would be clear when I said they are all generated in runtime, but I've edited to add a sentence to clarify that point. – durron597 Oct 2 '15 at 16:25
  • It seems to me that you need an interface also for the next level, but I could be wrong. – noumenal Oct 2 '15 at 16:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.