0

I use java and I have structure with a class that contains id, title and perhaps some children of the same class. So I decided to use the composite pattern.

I need to have a method getChildren() that gives me subtree from the whole.

I can not put this method in my interface because it is specific to the aggregator class.

If I put it in the interface, it will be implemented by the leaf class and I have to use a conditional if to check if my instance has children or not and I don't feel this is correct. Also in this approach I only need one class either for the aggregator or the leaf nodes which is not compatible with the pattern which need two classes. One for the leafs and one for the aggregators.

What is the correct approach to solve my problem? Maybe I have pick a wrong design pattern?

2
  • 6
    I don't really understand the problem here. Why would you need a conditional in your leaf class? Leafs don't have children. What stops you from returning List.emptyList() as your children? Maybe a more concrete example and some code would make the problem more clear
    – Helena
    Jan 28, 2022 at 8:39
  • Leafs and aggragator is the same class so I have to use conditional in order to decide whether I return List.emptyList() Jan 29, 2022 at 5:17

3 Answers 3

1

Your pattern choice seems perfectly appropriate. It seems your narrative fully matches the intent according to GoF:

Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individuals objects and compositions of objects uniformly.

Your question regarding getChildren() is one of the classical issues regarding the implementation of this pattern. GoF has two pages to discuss the pros and cons of the different ways to design the interface regarding children management. The conclusion is really, that it depends:

  • One one side there is a balance to be found between maximizing the component interface to make objects as uniform as possible (issue #3 in the book)
  • On the other side there is a trade-off to be found between uniformity and safety (operation on children only on composite class) (issue #4 in the book).

Regarding getChildren(), there is fortunately no safety issue at stake, so it's only a decision about how uniform you want to be: You may easily have a uniform getChildren() operation, that simply returns an empty collection for the leaves. It has the advantage of uniformity in many recursive or graph traversal algorithms, with no real risks/inconvenience.

If you don't need this uniformity, just don't do it. The main arbitration will anyway be about the other child management operations (adding and removing).

1

While the Composite pattern does result in a tree structure, that's not its primary motivation. It's not the one true wayTM to build a tree. It's purpose is to provide a way to treat the composite object the same way as an individual object (what would be a node, from a heterogeneous set of nodes).

Let me try and shift your thinking a bit from the tree metaphor. Imagine drawing shapes in a Word document (boxes, circles, triangles, arrows). These are individual objects that you can position, rotate, scale, etc. Now draw a bunch of them, and select the command to group them. Now you can position/rotate/scale the group as a whole. That group is a composite. That's something that fits perfectly with the Composite pattern.

If your goal is specifically to work with a more general purpose tree structure, then feel free to stray away from the pattern. If a single recursive class will do it, go with that. Have leaf nodes implement a way to return the child count, and return an empty list if child nodes are requested (this lets you avoid null checks, and null-related bugs). This is probably the way to go for what you're trying to do.

If your tree is somewhat a special-purpose tree that supports a limited set of operations, where you can just ask the tree to do things for you (instead of getting the individual nodes, reading their properties, and doing stuff with the nodes/tree in the calling code), then the Composite pattern might be a better fit.

For example, take the shape group mentioned above; think about how you'd implement the Draw operation. (My code snippets are going to use C# syntax, but just think of them as of pseudocode).

Inside the Composite class, you'd just forward the Draw call to each child (maybe after doing some offsets and rotations):

class Composite : IComponent {
  private List<IComponent> children = new List<IComponent>();

  // there's some way to add child shapes to the group (e.g. the ctor)

  // ...

  public Draw() {
    foreach(var child in children) {
      child.Draw();
    }
  }
}

This will then polymorphically call the Draw() method down the tree branches until a leaf is reached. A leaf is any of the individual concrete shape classes:

class Arrow : IComponent {

  public Draw() {
    // use some graphics API to draw an arrow
  }
}

Note that at no point there was a check to see if it's a composite or a leaf node. The polymorphic operations execute in the context of the object on which they are called, and the object itself knows if it's a leaf or not.

The calling code just looks like:

group.Draw();   // the entire group draws itself

You only have to check for the type/leaf if your operations are unable to make use of polymorphism, and are implemented outside of the tree structure, in the calling code (client code):

Draw(group);

// where Draw is implemented something like this:
void Draw(IComponent shape) {
  if (shape.ChildCount > 0) {
    foreach(var child in shape.Children) {
      Draw(child);
    }
  }
  else if (shape is Rectangle) {
    // cast, extract data, draw rectangle 
  }
  else if (shape is Circle) {
    // cast, extract data, draw circle
  }
  else if (shape is Arrow) {
    // cast, extract data, draw arrow
  }
  else if... { 
    // ...
  }
}

So, the two approaches make a tradeoff - one favors a special-purpose tree with a limited set of "smart" operations, the other a general-purpose bare-bones tree with only generic tree-related operations, leaving anything beyond that for the client code to implement. With the first one, it's easy to add new kinds of shapes, but it's not as easy to add new operations after-the-fact (e.g. if you published this as a library, and other people can derive new shapes). With the second one, the code is messier, and possibly error-prone and less expressive. And adding operations is easier than adding shapes (cause you'd have to hunt down every method to add a new case).

You can also opt for some combination of the two - have it lean more towards a general-purpose tree, but support a couple of useful smart operations. What's useful will depend on what your code/application is for.

For more advanced approaches (that can be an overkill, and come with their own set of tradeoffs), look into the Iterator and the Visitor patterns. Java Streams Philipp's answer mentions are a version of the Iterator pattern (what's called an internal iterator).

1
  • Thanks for the complete answer, I aggre, but still I have to deal with a tree and I need a method get Children which IMO seems not fitting in the composite pattern.I 'll take a look at visitor and iterator as you suggested. Thanks any way for the interesting answer. Jan 29, 2022 at 5:26
0

You could have a method GetValues which returns a Stream.

An aggregator node would implement that method by concatenating the streams returned by its children, while the leaf-node implementation would return a stream of only one entry.

This way you have an interface which even hides that the data-structure the consumer get their values from is internally structured as a tree.

Your Answer

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

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