Combination of structural patterns

Keywords: Programming Java less github

1 Overview

Composite Pattern refers to the grouping of a class of objects with common abstractions into a tree structure to represent the part-whole relationship.

2 Combination mode

Often we encounter objects with the same behavior and hierarchical structure between them.In this case, the combination mode can make the system highly cohesive and less coupled.By grouping classes into a tree structure, callers can treat individual objects and the overall structure in a uniform way, ignoring differences between the individual and the whole.Implementing a combination pattern generally requires:

  1. Define an interface/abstract class and define the common behavior of objects, including methods for maintaining hierarchies between objects.
  2. Define the entity of the object and implement the methods in the interface/abstract class.
  3. All entities are grouped together in a tree structure to form a whole.

3 Cases

Take an example.Employees in all companies are actually members of the same family with similar attributes and behaviors. At the same time, there is a hierarchical structure among employees, which is a good way to represent the relationship between employees with a combination model:

public class Test {
    public static void main(String[] args) {
        // Building hierarchies
        Director director = new Director("Polly");
        Manager frontendManager = new Manager("Lilei");
        Manager backendManager = new Manager("Hanmeimei");
        Engineer jsEngineer = new Engineer("Lily");
        Engineer ueDesigner = new Engineer("Lucy");
        Engineer javaEngineer = new Engineer("Jim");
        Engineer dbAdmin = new Engineer("Kate");
        director.addChild(frontendManager);
        director.addChild(backendManager);
        frontendManager.addChild(jsEngineer);
        frontendManager.addChild(ueDesigner);
        backendManager.addChild(javaEngineer);
        backendManager.addChild(dbAdmin);

        // Treat as a whole as a single object
        director.work();
    }
}

// Public methods for Employee are defined, and methods for adding/deleting nodes are defined
public interface Employee<T extends Employee> {
    String getName();
    Collection<T> getChildren() throws OperationNotSupportedException;
    void addChild(T employee) throws OperationNotSupportedException;
    void removeChild(T employee) throws OperationNotSupportedException;
    void work();
}
public class Director implements Employee<Manager> {
    private String name;
    private Collection<Manager> children = new ArrayList<>();
    Director(String name) {
        this.name = name;
    };
    [@Override](https://my.oschina.net/u/1162528)
    public String getName() {
        return name;
    }
    [@Override](https://my.oschina.net/u/1162528)
    public Collection getChildren() {
        return children;
    }
    [@Override](https://my.oschina.net/u/1162528)
    public void addChild(Manager employee) {
        children.add(employee);
    }
    [@Override](https://my.oschina.net/u/1162528)
    public void removeChild(Manager employee) {
        children.remove(employee);
    }
    // Loop-call method for child nodes
    [@Override](https://my.oschina.net/u/1162528)
    public void work() {
        System.out.println("Director " + name + " gives command to his subordinate...");
        for (Employee child : children) {
            child.work();
        }
    }
}
public class Manager implements Employee<Engineer> {
    private String name;
    private Collection<Engineer> children = new ArrayList<>();
    Manager(String name) {
        this.name = name;
    };
    @Override
    public String getName() {
        return name;
    }
    @Override
    public Collection getChildren() {
        return children;
    }
    @Override
    public void addChild(Engineer employee) {
        children.add(employee);
    }
    @Override
    public void removeChild(Engineer employee) {
        children.remove(employee);
    }
    // Loop-call method for child nodes
    @Override
    public void work() {
        System.out.println("Manager " + name + " gives command to his subordinates...");
        for (Employee child : children) {
            child.work();
        }
    }
}
public class Engineer implements Employee {
    private String name;
    Engineer(String name) {
        this.name = name;
    };
    @Override
    public String getName() { 
        return name; 
    }
    @Override
    public Collection getChildren() throws OperationNotSupportedException {
        throw new OperationNotSupportedException("No child under engineer.");
    }
    @Override
    public void addChild(Employee employee) throws OperationNotSupportedException {
        throw new OperationNotSupportedException("Can not add child for engineer.");
    }
    @Override
    public void removeChild(Employee employee) throws OperationNotSupportedException {
        throw new OperationNotSupportedException("Can not remove child for engineer.");
    }
    @Override
    public void work() {
        System.out.println("Engineer " + name + " is coding...");
    }
}

Output:

Director Polly gives command to his subordinate...
Manager Lilei gives command to his subordinates...
Engineer Lily is coding...
Engineer Lucy is coding...
Manager Hanmeimei gives command to his subordinates...
Engineer Jim is coding...
Engineer Kate is coding...

Using the combination mode, we can operate on the whole and the individual objects with consistent behavior, which significantly reduces the complexity of the system.At the same time, the tree structure is easy to expand for future maintenance.

java.awt.Container uses the combination mode, and between components is a tree structure, and Container is the root node of all components:

public class Container extends Component {
    private java.util.List<Component> component = new ArrayList<>();
    // add component
    protected void add(Component comp, Object constraints, int index) {
        ...
        //index == -1 means add to the end.
        if (index == -1) {
            component.add(comp);
        } else {
            component.add(index, comp);
        }
        comp.parent = this;
        comp.setGraphicsConfiguration(thisGC);
        ...
    }
    // Remove Components
    public void remove(int index) {
        synchronized (getTreeLock()) {
            ...
            component.remove(index);
            comp.setGraphicsConfiguration(null);
            ...
        }
    }
    // Call the updateGraphicsData method and the updateGraphicsData method of the child node
    boolean updateGraphicsData(GraphicsConfiguration gc) {
        boolean ret = super.updateGraphicsData(gc);
        for (Component comp : component) {
            if (comp != null) {
                ret |= comp.updateGraphicsData(gc);
            }
        }
        return ret;
    }
    ...
}

4 Summary

When there is a hierarchical relationship between objects, you can consider using the combination mode to unify the single object and the combined object to reduce the system complexity.

The github address of the example in this article

Posted by dpsd on Sun, 05 Apr 2020 11:38:25 -0700