11

In terms of good OOP design, what is the best way to structure code that is just containers of list of objects that contains other lists of objects that also are just other containers?

Example: A Cookbook owns a list of Chapters that owns a list of Recipes that owns a list of Directions. Each object is a different layer within a whole. For clarity, I use the word "list" to refer to any data structure used to store multiple objects.

I've come across this Russian doll problem of nested objects inside of nested objects many times. I've tried passing the bottom level objects through the nest hierarchy and also tried writing methods that pass information down to the bottom nested object. My structure always feels messy because the intermediate classes are filled with methods that do nothing more than passing either objects or information.

An addition due to confusion expressed in the comments: I'm looking for direction in things to study for handling a general type of problem. I'm not asking for a solution to a specific coding problem that I have in front of me. I wasn't expecting a "one answer to rule them all", but more of "use A design pattern for X situations but use B design pattern for Y situations and beware of Z." The example is for providing a general context, not a specific one.

16
  • 1
    It seems strange to me that a Menu object would have as its grand-children, Ingredient objects. OOP is supposed to be a model for real-world thinking, so when was the last time you saw a menu where all the dishes listed all of their ingredients? Perhaps try something like the Menu object holding DishSummary items, and a helper class can take a DishSummary object and return a DishDetail object (with Ingredients), if needed
    – GHP
    Commented Sep 6, 2016 at 19:10
  • @Graham I OP is not disclosing the real domain his code is applied to and that the menu example is just that, an example. Commented Sep 6, 2016 at 19:12
  • Are you talking about a scenario where the top level object owns all the nested objects, or merely has references to them? As @Graham says, it doesn't make much sense for a Menu to own all the Ingredients. Is there also a Cookbook in your hypothetical scenario? Commented Sep 6, 2016 at 19:18
  • 1
    Unclear for me what the problem is. It's like asking "what's the best way to create russian dolls but without directly creating russian dolls".
    – Spotted
    Commented Sep 8, 2016 at 5:35
  • 1
    @WP0987: for discussions, you are welcome to try The Whiteboard. The SE sites, however, are not discussion forums, they are Q&A sites. Here on "Programmers", we try to find the best possible answer to conceptual programming questions. The best questions are those for actual, real problems people have. Questions which only lead to open ended discussions with no possibility of finding a "best" answer are typically closed by the community here.
    – Doc Brown
    Commented Sep 8, 2016 at 21:10

2 Answers 2

2

One way to make it easier is that all classes implement the same interface so you can treat them indistinctly. Theres a known pattern for this:

enter image description here

The good news is that as this creates a tree. You can then write, in an external managing class, recursive methods to look up items.

For example you could write a method findAndAdd(String tagToFind, Component componentToAdd);

To the base interfaces documented in the pattern you should add a tagMatches(String tag) as well as all common getters and setters so you can, again, treat items indistincltly of their class.

I would add hasChild() and nextChild() methods to implement iterations.

11
  • Is i just an index to the (i-1)ᵗʰ item (for zero-based arrays)? Commented Sep 6, 2016 at 19:10
  • 7
    -1, abusing the composite pattern for a fixed hierarchy of different levels of non recursive structured objects is nothing but an example of overengineering.
    – Doc Brown
    Commented Sep 6, 2016 at 20:03
  • 1
    @WP0987: see my answer below. You are IMHO approaching this from the wrong side, your data structure is probably ok, instead you may consider to implement the code which accesses it differently.
    – Doc Brown
    Commented Sep 6, 2016 at 20:31
  • 3
    And if all the objects in the hierarchy have different capabilities (ie different methods) ? It would mean you either have to declare all of them in the interface, leading to unimplemented methods in some classes (code smell) or declare them in their respective classes but since you unified the whole hierarchy under one interface they are not directly visible. So you'll have to crazy-cast everywhere to reach these methods (again, bigger code smell). That's how a design pattern becomes an anti-pattern...
    – Spotted
    Commented Sep 8, 2016 at 6:17
  • 1
    @TulainsCórdova Yep, that's why I said "if" because it's not sure at the moment that my comment above is relevant (at least as long as the OP doesn't provide more details).
    – Spotted
    Commented Sep 8, 2016 at 6:58
17

Do yourself a favor, and keep it simple and stupid!

In your example, you wanted to model Cookbooks, having Chapters, containing Recipes, owning Directions". In terms of good OO design, you model exactly this: a class Cookbook with an aggregate list of Chapters, having an aggregate list of Recipes, each one with a list of Directions. This is a straightforward mapping from "real world objects" to "OOP objects", which is one of the core ideas of object oriented modeling. It is easy to understand and maintain for you and anyone else who has to deal with this data.

At this point, your code is not yet messy. It might become messy if you often have to write methods like

 // inside a Cookbook method:
 foreach chapter in listChapters
     foreach recipe in  chapter.listRecipes
          foreach  direction in  recipe.listDirection
              // do something with direction                

You can avoid writing such code more than once if your programming language has good support for creating your own iterator function (like C# or Python). Then you can easily provide resuable helper functions once for getting, for example, all directions in a cookbook. Another approach it to utilize the visitor pattern, it will allow you to provide different operations "visiting" all objects in that hierarchy and do "something" during the visit.

Of course, when you have uniform containers, where on each level of the hierarchy a container can hold a list of containers of the same type, then the composite pattern suggested in @TulainsCórdova answer makes sense. Examples for this are a file/folder hierarchy, or a tree structure for abstract syntax trees, where expressions contain subexpressions which contain subexpressions, and so on. But in your example, each of the container object you described on each level is different from each object on another level, it has a different name, probably a different behaviour, and contains only sub containers of one specific type. So trying to shoehorn the described case into the composite pattern will probably make your code really messy.

2
  • Needs more details: could you please elaborate on how you think the helper functions might look? Mainly, does Cookbook know about ingredients, or only about recipes? How to navigate the layers?
    – user949300
    Commented Sep 7, 2016 at 21:16
  • 3
    @user949300: it would be helpful if the OP would give an understandable example where his code "becomes messy" (as he wrote), then I could probably tell him how to tailor an iterator for this. But so far the ony response I see that he accepted a clearly overengineered answer which does not really fit to his question in favor of mine. So I have to assume I might not have understood his intentions correctly.
    – Doc Brown
    Commented Sep 8, 2016 at 5:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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