简体   繁体   中英

How to handle functions for add, delete in composite pattern?

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 ie 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.

在此处输入图片说明

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
);

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM