2

Composite pattern is useful for handling part-whole hierarchies. It has an interface for the Component. Leaf and Composite both provide implementation for the Component interface.

What should be the implementation for Add(Component c), Remove(Component c) and GetChild(int position) methods in Leaf class ?

I can have methods do nothing, or throw an exception like : OperationNotSuportedByLeafException. But Doing this will break the Liskov substitution principle. What is the best way to handle these methods?

Edit: Another approach is moving these methods in Composite. It will be the topmost interface being exposed i.e. Component. Moving the methods in Composite will require explicit casting, when there will be need to call the add, remove operations, which again is against good design principle.

enter image description here

nits.kk
  • 5,204
  • 4
  • 33
  • 55

2 Answers2

2

Depends of course what your design goals are. If your goal is that the tree/graph should be modifiable at any point in time, then a leaf can actually become a parent node. In this case, it is OK to define hierarchy-relevant methods in the component.

Another approach (although I do not know if this is applicable to your use-case) is to use following two ideas:

  • Make the structure immutable
  • Separate structure from function

By making the structure immutable, we can push all graph construction to, well, the constructors. With this we sidestep the type-casting issue you mentioned, and also make the whole thing easier to reason about.

By separating the structure from function, we don't need to publish the structural information at all to clients, we instead offer the functionality we want to offer. With this we can keep Liskov, Demeter and the other OO things.

This is how it can look like:

public interface Node {
    // Here we offer the "functionality", but we don't
    // publish the "structure". This is made-up, I
    // don't know your use-case.
    void process(Consumer<Payload> payloadConsumer);
}

public class Leaf implements Node {
    private Payload payload;

    public Lead(Payload payload) {
        this.payload = payload;
    }

    @Override
    public void process(Consumer<Payload> payloadConsumer) {
        payloadConsumer.accept(payload);
    }
}

public class ParentNode implements Node {
    private List<Node> children;

    public ParentNode(Node... children) {
        this.children = asList(children);
    }

    // Here we implement the processing recursively.
    // All children can respond according to their semantics,
    // instead of assuming any structure beyond what we know.
    @Override
    public void process(Consumer<Payload> payloadConsumer) {
       children.forEach(child -> child.process(payloadConsumer));
    }
}

Of course you can define your own Node types depending on what kind of logic you want to represent. You can define multiple operations, not just the one process() method I made up. Then you can plug all that together like this:

Node graph = new ParentNode(
   new ParentNode(new Leaf(p1), new Leaf(p2)),
   new SpecialLeaf(a, b, c) // Whatever
);
Robert Bräutigam
  • 7,514
  • 1
  • 20
  • 38
  • I liked the idea of putting addtion of leaves in cunstructor of Composite. But usually in ui based use cases, we may have for exampl TextView, Button, Label (each of type leaf) and other iews like LinearLayout , RelativeLayout (each of type Composite), in such cases leaves will never become Composite but ges at run time leaves and composites can be added or removed to/from a parent Composite. Hence constructor approach will not work – nits.kk Jun 02 '16 at 08:48
  • Great example! Out of curiosity why do you call it a `Consumer` instead of a `Visitor`? – cyroxis Jun 02 '16 at 14:04
  • It's just because `java.util.function.Consumer` is already there, and fits this example's purpose. – Robert Bräutigam Jun 02 '16 at 14:25
1

There are two ways to solve this issue.

  1. Throw OperationNotSuportedByLeafException. You can debate if this breaks LSP. Personally I think it should be avoided but sometimes it is the best solution (See Java's immutable lists for example). The LSP is a principle meant to help you write better quality code. Every system has warts and this may be one of them.

  2. Move add & remove to Composite entity (example). This is what you would typically see in a view library. A View may be a block of text or it may be a complex layout full of many other views.

shmosel
  • 49,289
  • 6
  • 73
  • 138
cyroxis
  • 3,661
  • 22
  • 37
  • #2. You can't add or remove anything to/from a leaf -- move the code to Composite. – dbugger Jun 01 '16 at 18:45
  • @cyroxis, yes 2nd option is also a way and I will edit my question to include that as well. But it also has an issue, It will be the Component interface which will be exposed and in order to call the add, remove operation an explicit typecasting will be needed and again its against good design principles. – nits.kk Jun 01 '16 at 18:46
  • 1
    @nits.kk Why is explicit typecasting needed? That is not required for the pattern (may be for your design if so update your question with more detail). Even so you will often find that good design principles can conflict with one another. Not every problem has a clean solution, sometimes you pick the least messy. – cyroxis Jun 01 '16 at 19:04
  • I modified the question. Explicit typecasting will be needed as it will be top most interface Component which will be exposed, composite pattern has the intention of representing whole part hierarcy annd treating both the leaf and composite similarly... – nits.kk Jun 02 '16 at 03:19
  • It sounds like the way you are planning on using the composite structure will involve either `casting` or `UnsuportedOperationException` in either case you have an error if you do something wrong. Example: Android uses casting after findViewById, but for rendering and layout it does not matter to a parent if a child is a leaf or a composite structure. It works pretty well due to how it is used, and errors early (view binding) verse late (view action). – cyroxis Jun 02 '16 at 14:01