introduce

Composite Pattern, also known as Part Whole Pattern, belongs to structural design Pattern. The Composite Pattern is relatively simple. It treats a group of similar objects as one object and combines objects according to a tree structure. It then provides a unified way to access the corresponding objects, ignoring the differences between objects and collections of objects.

define

Objects are grouped into a tree structure to represent a partial-whole hierarchy so that users can consistently use whole objects and composite objects.

Usage scenarios

  • Represents a partial-whole hierarchy of objects.
  • A scenario in which some modules or functions can be separated from a whole.

UML class diagrams

  • Component: Abstracts the root node, declaring interfaces for objects in the composition. Where appropriate, implement the default behavior of interfaces common to all classes. Declare an interface to access and manage Component child nodes. You can define an interface in a recursive structure to access a parent node and implement it as appropriate.
  • Composite: Defines the behavior of branch nodes that have child nodes, stores child nodes, and implements child node related operations in the Component interface.
  • Leaf: Represents a Leaf node object in a combination. A Leaf node has no child nodes and defines the behavior of the node object in the combination.
  • Client: Objects that manipulate composite nodes through the Component interface.

Code sample

A simple example

Abstract root node:

public abstract class Component {


    /** * Node name */
    protected String name;

    public Component(String name) {
        this.name = name;
    }

    /** * The specific logical methods are implemented by subclasses */
    public abstract void doSomething(a);

    /** * Add child node *@param component
     */
    public abstract void addChild(Component component);

    /** * Remove child node *@param component
     */
    public abstract void removeChild(Component component);

    /** * get the child node *@param index
     * @return* /
    public abstract Component getChildren(int index);

}
Copy the code

Branch node:

public class Composite extends Component {
    /** * Storage node container */
    private List<Component> mLists = new ArrayList<>();


    public Composite(String name) {
        super(name);
    }

    @Override
    public void doSomething(a) {
        System.out.println("name: " + name);
        if(mLists ! =null && mLists.size() > 0) {
            for(Component component : mLists) { component.doSomething(); }}}@Override
    public void addChild(Component component) {
        mLists.add(component);
    }

    @Override
    public void removeChild(Component component) {
        mLists.remove(component);
    }

    @Override
    public Component getChildren(int index) {
        returnmLists.get(index); }}Copy the code

Leaf node:

public class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }

    @Override
    public void doSomething(a) {
        System.out.println("name: " + name);

    }

    @Override
    public void addChild(Component component) {
        throw new UnsupportedOperationException("Leaf node has no children");
    }

    @Override
    public void removeChild(Component component) {
        throw new UnsupportedOperationException("Leaf node has no children");
    }

    @Override
    public Component getChildren(int index) {
        throw new UnsupportedOperationException("Leaf node has no children"); }}Copy the code

test:

    @Test
    public void testComponent(a) {
        // Construct a root node
        Component root = new Composite("Root");

        // Construct branch nodes
        Component branchA = new Composite("branchA");
        Component branchB = new Composite("branchB");

        // Construct the leaf node
        Component leafA = new Leaf("leafA");
        Component leafB = new Leaf("LeafB");

        // Add the leaf node to the branch node
        branchA.addChild(leafA);
        branchB.addChild(leafB);

        // Add the stem node to the heel node
        root.addChild(branchA);
        root.addChild(branchB);

        root.doSomething();
    }
Copy the code

output:

name: Root
name: branchA
name: leafA
name: branchB
name: LeafB
Copy the code

In actual combat

Business background: use code to represent the combination of folders and files;

File and folder abstract classes:

public abstract class Dir {
    /** * declare a List member variable to store all the elements under the folder */
    protected List<Dir> mDirs = new ArrayList<>();

    /** * Current file or folder name */
    protected String name;

    public Dir(String name) {
        this.name = name;
    }

    /** * Add a file or folder *@param dir
     */
    public abstract void addDir(Dir dir);

    /** * Remove a file or folder *@param dir
     */
    public abstract void removeDir(Dir dir);

    /** * Clear all the elements under the folder */
    public abstract void clear(a);

    /** * outputs the directory structure under the folder */
    public abstract void print(a);

    /** * get all files or subfolders in the folder */
    public abstract List<Dir> getFiles(a);

    /** * Get the file or folder name *@return* /
    public String getName(a){
        returnname; }}Copy the code

Specific folder class:

public class Folder extends Dir {
    public Folder(String name) {
        super(name);
    }

    @Override
    public void addDir(Dir dir) {
        mDirs.add(dir);

    }

    @Override
    public void removeDir(Dir dir) {
        mDirs.remove(dir);
    }

    @Override
    public void clear(a) {
        mDirs.clear();
    }

    @Override
    public void print(a) {
        System.out.print(getName() + "(");
        Iterator<Dir> iterator = mDirs.iterator();
        while (iterator.hasNext()) {
            Dir dir = iterator.next();
            dir.print();
            if (iterator.hasNext()) {
                System.out.print(",");
            }
        }
        System.out.print(")");
    }

    @Override
    public List<Dir> getFiles(a) {
        returnmDirs; }}Copy the code

Specific file classes:

public class File extends Dir {
    public File(String name) {
        super(name);
    }

    @Override
    public void addDir(Dir dir) {
        throw new UnsupportedOperationException("File object does not support this operation");
    }

    @Override
    public void removeDir(Dir dir) {
        throw new UnsupportedOperationException("File object does not support this operation");
    }

    @Override
    public void clear(a) {
        throw new UnsupportedOperationException("File object does not support this operation");
    }

    @Override
    public void print(a) {
        System.out.println("name: "+name);
    }

    @Override
    public List<Dir> getFiles(a) {
        throw new UnsupportedOperationException("File object does not support this operation"); }}Copy the code

test:

    @Test
    public void testComponent2(a){
        // Construct a directory object representing the sdcard disk and directory
        Dir diskSdcard = new Folder("sdcard");

        // there is a file under sdcard
        diskSdcard.addDir(new File("bug.log"));

        // There is also a subdirectory android under sdcard
        Dir android =  new Folder("android");
        // There is a directory in the android directory
        Dir data = new Folder("data");
        data.addDir(new File("2019-09-14.log"));
        android.addDir(data);
        diskSdcard.addDir(android);

        // There is a subdirectory baidu under sdcard
        Dir baidu =  new Folder("baidu");
        // There is a directory in the android directory
        Dir cache = new Folder("cache");
        cache.addDir(new File("offline.baidu"));
        baidu.addDir(cache);
        diskSdcard.addDir(baidu);

        // There is also a subdirectory called downloads under sdcard
        Dir downloads =  new Folder("downloads");
        // There is a directory in the android directory
        Dir f360 = new Folder("360");
        f360.addDir(new File("360.log"));
        downloads.addDir(f360);
        diskSdcard.addDir(downloads);

        // Prints the file structure
        diskSdcard.print();
    }
Copy the code

output:

sdcard (
  / / file
  name: bug.log, 
  / / folder
  android (data (name: 2019-09-14.log)),
  baidu (cache (name: offline.baidu)), 
  downloads (360 (name: 360.log))
)
Copy the code

Here we use parentheses as the content range of a folder. As shown above, the folder under Adcard has three sub-folders, Android, Baidu, downloads, and a bug.log file, and the three sub-folders also contain sub-folders and files respectively. A typical tree-like nested decoupling is a composite pattern.

conclusion

Advantages:

  • The composite pattern can clearly define the hierarchical complex object, representing all or part of the object’s hierarchy, it allows the high-level module to ignore the differences of the hierarchy, convenient control of the entire hierarchy.
  • High-level modules can consistently use a composite structure or a single object within it, regardless of whether they are dealing with a single object or the entire composite structure, simplifying the code for high-level modules.
  • It is convenient to add new branches and leaves to the composite pattern without any modifications to the existing libraries, following the “open closed principle”.
  • The combination pattern provides a flexible solution for the object-oriented implementation of tree structure. Through the recursive combination of leaf object and trunk object, complex tree structure can be formed, but the control of tree structure is very simple.

Disadvantages:

You can’t rely on the type system to impose these constraints because in most cases, they come from the abstraction layer you want to hear, and you have to do type checking to implement them, which can be complicated.

Article code address

Special thanks to

Android source code design pattern analysis and combat

Thank you for reading, thank you!