Composite Pattern in Java and Its Application Scenarios
Category Programming Technology
The composite pattern involves containing objects within other objects, where these contained objects can be either terminal (do not contain other objects) or non-terminal (contain other objects, also known as group objects). We refer to these objects as nodes, meaning a root node contains many child nodes, some of which may not contain further child nodes, while others do, and so on. This clearly forms a tree structure, where terminal nodes are called leaf nodes, non-terminal nodes (group nodes) are called branch nodes, and the first node is the root node. It also resembles the structure of a file directory: files can be considered terminal nodes, and directories can be considered non-terminal nodes (group nodes).
Ordinary Implementation
- Let's first look at an ordinary implementation of a directory structure:
Directory Node: Noder
import java.util.ArrayList;
import java.util.List;
/**
* Directory Node
* Contains:
* 1. Directory name
* 2. List of sub-files
* 3. List of sub-directories
* 4. Method to add a file
* 5. Method to add a directory
* 6. Method to display sub-contents
*/
public class Noder {
String nodeName; // Directory name
// Constructor to name the directory
public Noder(String nodeName) {
this.nodeName = nodeName;
}
List<Noder> nodeList = new ArrayList<Noder>(); // List of sub-directories
List<Filer> fileList = new ArrayList<Filer>(); // List of sub-files
// Add a sub-directory
public void addNoder(Noder noder) {
nodeList.add(noder);
}
// Add a file
public void addFiler(Filer filer) {
fileList.add(filer);
}
// Display sub-directories and files
public void display() {
for (Noder noder : nodeList) {
System.out.println(noder.nodeName);
noder.display(); // Recursively display directory list
}
for (Filer filer : fileList) {
filer.display();
}
}
}
File Node: Filer
/**
* File Node
* File nodes are terminal nodes, with no sub-nodes
* Contains:
* 1. File name
* 2. File display method
*/
public class Filer {
String fileName; // File name
public Filer(String fileName) {
this.fileName = fileName;
}
// File display method
public void display() {
System.out.println(fileName);
}
}
Test Class: Clienter
import java.io.File;
public class Clienter {
public static void createTree(Noder node) {
File file = new File(node.nodeName);
File[] f = file.listFiles();
for (File fi : f) {
if (fi.isFile()) {
Filer filer = new Filer(fi.getAbsolutePath());
node.addFiler(filer);
}
if (fi.isDirectory()) {
Noder noder = new Noder(fi.getAbsolutePath());
node.addNoder(noder);
createTree(noder); // Use recursion to generate tree structure
}
}
}
public static void main(String[] args) {
Noder noder = new Noder("E://ceshi");
createTree(noder); // Create directory tree structure
noder.display(); // Display directories and files
}
}
Execution Result:
E:\ceshi\目录1
E:\ceshi\目录1\目录3
E:\ceshi\目录1\文件2.txt
E:\ceshi\目录2
E:\ceshi\目录2\文件3.txt
E:\ceshi\文件1.txt
2. Composite Pattern
From the above code, we can see that we have defined file node objects and directory node objects separately, because files and directories have different operations. Files do not have sub-nodes, while directories can have sub-nodes. However, we can consider that both files and directories can exist as sub-nodes of a node. Although their operations are different, we can define them in the method implementation of the classes, such as throwing an exception in the file's method for adding sub-nodes, without specific implementation, while directories implement the addition operation specifically. Both have display operations and can be implemented individually. Since we have abstracted files and directories into a single type, we can use polymorphism to implement as follows:
Abstract Class: Node
/**
* Treat files and directories as a type of node, define an abstract class for this node, and use its implementation classes to distinguish files and directories, defining their specific implementations in these classes
*/
public abstract class Node {
protected String name; // Name
// Constructor to assign a name
public Node(String name) {
this.name = name;
}
// Add node: Files do not have this method, directories override this method
public void addNode(Node node) throws Exception {
throw new Exception("Invalid exception");
}
// Display node: Both files and directories implement this method
abstract void display();
}
File Implementation Class: Filer
/**
* Implement file node
*/
public class Filer extends Node {
// Constructor to name the file node
public Filer(String name) {
super(name);
}
// Display file node
@Override
public void display() {
System.out.println(name);
}
}
Directory Implementation Class: Noder
import java.util.*;
/**
* Implement directory node
*/
public class Noder extends Node {
List<Node> nodeList = new ArrayList<Node>(); // Internal node list (including files and sub-directories)
// Constructor to name the current directory node
public Noder(String name) {
super(name);
}
// Add node
public void addNode(Node node) throws Exception {
nodeList.add(node);
}
// Recursively display sub-nodes
@Override
void display() {
System.out.println(name);
for (Node node : nodeList) {
node.display();
}
}
}
Test Class: Clienter
import java.io.File;
public class Clienter {
public static void createTree(Node node) throws Exception {
File file = new File(node.name);
File[] f = file.listFiles();
for (File fi : f) {
if (fi.isFile()) {
Filer filer = new Filer(fi.getAbsolutePath());
node.addNode(filer);
}
if (fi.isDirectory()) {
Noder noder = new Noder(fi.getAbsolutePath());
node.addNode(noder);
createTree(noder); // Use recursion to generate tree structure
}
}
}
public static void main(String[] args) {
Node noder = new Noder("E://ceshi");
try {
createTree(noder);
} catch (Exception e) {
e.printStackTrace();
}
noder.display();
}
}
Execution Output Result:
E://ceshi
E:\ceshi\文件1.txt
E:\ceshi\目录1
E:\ceshi\目录1\文件2.txt
E:\ceshi\目录1\目录3
E:\ceshi\目录2
E:\ceshi\目录2\文件3.txt
From the above implementation, it can be seen that the composite pattern involves objects containing other objects, achieved through composition (referencing objects within objects). I believe this composition is distinct from inheritance. Another layer of meaning refers to the abstraction of child nodes in a tree structure (abstracting leaf nodes and branch nodes as child nodes), which differs from the conventional approach of defining leaf nodes and branch nodes separately.
3. Application Scenarios of Composite Pattern
This composite pattern is designed for tree structures, so its application scenarios are where tree structures appear. For example: displaying file directories, presenting multi-level directories, and other tree-structured data operations.
Original Source: https://www.cnblogs.com/V1haoge/p/6489827.html