Overview of the Composite Strategy
This structural design pattern facilitates the construction of hierarchical trees representing part-whole relationships. Its primary goal is to allow client applications to process both single elements and groups of elements through a unified interface. By defining a common abstract type, the system treats leaf nodes (individual items) and branch nodes (collections) consistent, simplifying recursive operations.
Structural Implementation
To achieve this architecture, we define an abstract base class that declares standard operations for all node types. Concrete classes then inherit this interface, implementing specific behaviors for either containers or leaves.
1. Abstract Component Definition
The foundation of the hierarchy is the AbstractNode class. It establishes the contract to manipulation, including adding subordinates, retrieving children, and rendering output. Default implementations raise exceptions for leaf nodes, enforcing stricter overrides in container classes.
package org.sample.composite.struct;
public abstract class AbstractNode {
private String label;
public AbstractNode(String label) {
this.label = label;
}
public void attach(AbstractNode child) {
throw new UnsupportedOperationException("Leaf nodes cannot have children");
}
public void detach(AbstractNode child) {
throw new UnsupportedOperationException("Leaf nodes do not manage children");
}
public AbstractNode getChild(int index) {
throw new UnsupportedOperationException("Leaf nodes do not expose children");
}
public String getLabel() {
return label;
}
public String getDetails() {
return null;
}
public double getValue() {
return 0.0;
}
public boolean isVeggieFriendly() {
return false;
}
public void display() {
throw new UnsupportedOperationException("Implementation required in subclass");
}
}
2. Composite Container Class
The GroupNode acts as a parent capable of storing multiple descendants. It utilizes a list to manage references and overrides methods to enable traversal and modification of the subtree.
package org.sample.composite.struct;
import java.util.ArrayList;
import java.util.List;
public class GroupNode extends AbstractNode {
private final List<AbstractNode> children = new ArrayList<>();
private final String summary;
public GroupNode(String label, String summary) {
super(label);
this.summary = summary;
}
@Override
public void attach(AbstractNode child) {
children.add(child);
}
@Override
public void detach(AbstractNode child) {
children.remove(child);
}
@Override
public AbstractNode getChild(int index) {
if (index < 0 || index >= children.size()) {
return null;
}
return children.get(index);
}
public String getSummary() {
return summary;
}
@Override
public void display() {
System.out.println("▸ " + getLabel());
System.out.println(" (" + getSummary() + ")");
System.out.println("-----------------------------------");
// Iterating recursively through child nodes
children.forEach(AbstractNode::display);
}
}
3. Leaf Node Implementation
The TextNode represents the terminal element within the structure. It holds specific data attributes like price and deitary information but does not contain other nodes.
package org.sample.composite.struct;
public class TextNode extends AbstractNode {
private String details;
private boolean veggie;
private double cost;
public TextNode(String label, String details, boolean veggie, double cost) {
super(label);
this.details = details;
this.veggie = veggie;
this.cost = cost;
}
public String getDetails() {
return details;
}
@Override
public double getValue() {
return cost;
}
@Override
public boolean isVeggieFriendly() {
return veggie;
}
@Override
public void display() {
StringBuilder line = new StringBuilder();
line.append(" ").append(getLabel());
if (isVeggieFriendly()) {
line.append(" (Vegetarian)");
}
line.append(" - $").append(String.format("%.2f", getValue()));
System.out.println(line.toString());
System.out.println(" Description: " + getDetails());
}
}
Client Interaction and Testing
The external controller interacts solely with the abstract type. This decouples the client logic from the concrete class specifics, allowing dynamic restructuring of the tree without modifying processing code.
4. Control Handler
package org.sample.composite.struct;
public class OrderController {
private final AbstractNode rootStructure;
public OrderController(AbstractNode rootStructure) {
this.rootStructure = rootStructure;
}
public void renderSelection() {
System.out.println("Loading Complete Catalogue...");
rootStructure.display();
System.out.println("Catalogue Finished.");
}
}
5. Execution Driver
The following setup demonstrates creating a multi-level hierarchy, populating it with data, and invoking the unified display method.
package org.sample.composite.struct;
public class MainRunner {
public static void main(String[] args) {
// Define sub-groups
AbstractNode breakfast = new GroupNode("Morning Meals", "Start your day");
AbstractNode lunch = new GroupNode("Midday Options", "Fast service");
AbstractNode drinks = new GroupNode("Beverages", "Refreshments");
// Define the total collection
AbstractNode fullCollection = new GroupNode("Full Restaurant Menu", "All Departments");
// Add items to Breakfast group
breakfast.attach(new TextNode("Omelette", "Eggs with cheese", true, 4.50));
breakfast.attach(new TextNode("Hash Browns", "Fried potato sticks", false, 2.00));
// Add items to Lunch group
lunch.attach(new TextNode("Club Sandwich", "Three-layered meat sandwich", false, 8.00));
// Nesting structure: Drinks as a child of Beverages category
drinks.attach(new TextNode("Latte", "Coffee with steamed milk", false, 3.50));
lunch.attach(drinks);
// Assemble the top level
fullCollection.attach(breakfast);
fullCollection.attach(lunch);
// Process via client
OrderController server = new OrderController(fullCollection);
server.renderSelection();
}
}