Composite
The Composite Pattern enables us to collect objects into trees that can be easily explored. The Parent represents the access point to the tree, where it in fact will be a Container, usually. The container will be able to contain other Container objects or Leaf objects.
Often the sublcasses or the leaves can be instantiated thorugh the various factory patterns.
Usage
Typical example is the file system. every folder and every single file are files. Folders contain also other files.
Examples
- File system
Specimens
15 implementationsThe Composite pattern allows you to treat individual objects and compositions of objects uniformly. It defines a tree-like structure where each node can be either a leaf (individual object) or a composite (group of objects). This enables you to perform operations on entire structures and their components in a consistent manner.
This Dart implementation defines an Entry interface with a getCost() method. File implements Entry as a leaf node. Folder also implements Entry, but contains a list of other Entry objects, allowing it to be a composite. The main() function demonstrates creating a file system structure, adding files and folders, and calculating the total cost. The use of interfaces and list-based composition is idiomatic to Dart and facilitates flexibility and scalability. Using abstract classes for both File and Folder provides a clean boundary for defining common behavior.
abstract class Entry {
double getCost();
}
class File implements Entry {
final String name;
final double size;
File(this.name, this.size);
@override
double getCost() {
return size;
}
}
class Folder implements Entry {
final String name;
final List<Entry> entries = [];
Folder(this.name);
void add(Entry entry) {
entries.add(entry);
}
@override
double getCost() {
return entries.fold(0.0, (sum, entry) => sum + entry.getCost());
}
}
void main() {
Folder root = Folder('Root');
Folder documents = Folder('Documents');
Folder pictures = Folder('Pictures');
File file1 = File('Report.txt', 100.0);
File file2 = File('Photo.jpg', 50.0);
File file3 = File('Presentation.pptx', 150.0);
documents.add(file1);
pictures.add(file2);
pictures.add(file3);
root.add(documents);
root.add(pictures);
print('Total cost: ${root.getCost()}');
}
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It defines class hierarchies where you can form tree structures of components. In this case, we represent a file system where files and directories can both be treated as “File System Entities”. The FileSystemEntity is the common interface. File represents a leaf node, while Directory represents a composite node holding other entities. This Scala implementation leverages inheritance and a common trait (FileSystemEntity) for a clean and flexible structure, fitting Scala’s functional-object oriented style by using traits for defining the interface and classes for concrete implementations.
trait FileSystemEntity {
def name: String
def size: Long
def printDetails(indent: String = ""): Unit
}
class File(val name: String, val size: Long) extends FileSystemEntity {
def printDetails(indent: String = ""): Unit = {
println(s"${indent}File: $name, Size: $size bytes")
}
}
class Directory(val name: String, val children: List[FileSystemEntity] = Nil) extends FileSystemEntity {
def size: Long = children.map(_.size).sum
def add(entity: FileSystemEntity): Directory = new Directory(name, children :+ entity)
def printDetails(indent: String = ""): Unit = {
println(s"${indent}Directory: $name, Total Size: $size bytes")
children.foreach(child => child.printDetails(indent + " "))
}
}
object FileSystemExample {
def main(args: Array[String]): Unit = {
val root = new Directory("Root")
val documents = root.add(new Directory("Documents"))
val photos = root.add(new Directory("Photos"))
documents.add(new File("report.docx", 1024))
documents.add(new File("presentation.pptx", 2048))
photos.add(new File("vacation.jpg", 5120))
photos.add(new File("family.png", 2560))
root.printDetails()
}
}
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It’s useful when you have hierarchical structures where you want to perform the same operation on both individual elements and groups of elements. Here, we represent a file system structure, with files and directories. Both files and directories ‘display’ themselves (their names/contents), but directories can contain other files or directories. The code utilizes interfaces for common operations and implements a tree-like structure through composition rather than inheritance, fundamental to the Composite pattern. This fits PHP’s flexible typing and interface-oriented approach.
<?php
interface FileSystemNode {
public function display(int $indent = 0): void;
}
class File implements FileSystemNode {
private string $name;
public function __construct(string $name) {
$this->name = $name;
}
public function display(int $indent = 0): void {
echo str_repeat(" ", $indent) . "- " . $this->name . "\n";
}
}
class Directory implements FileSystemNode {
private string $name;
private array $children = [];
public function __construct(string $name) {
$this->name = $name;
}
public function add(FileSystemNode $node): void {
$this->children[] = $node;
}
public function display(int $indent = 0): void {
echo str_repeat(" ", $indent) . "+ " . $this->name . "\n";
foreach ($this->children as $child) {
$child->display($indent + 2);
}
}
}
// Example usage:
$root = new Directory("Root");
$root->add(new File("file1.txt"));
$subdir = new Directory("Subdirectory");
$subdir->add(new File("file2.txt"));
$subdir->add(new File("file3.txt"));
$root->add($subdir);
$root->add(new File("file4.txt"));
$root->display();
?>
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It defines a tree-like structure where each node can be either a leaf (individual object) or a branch (composite object containing other nodes). This removes the need for different code to handle individual objects versus groups of objects.
Here, we represent a file system. Entry is the base class, and File is a leaf node. Directory is a composite node that can contain other Entry objects (files and directories). The size method is implemented in Entry and recursively calculates the total size in Directory, demonstrating the uniform interface. Ruby’s dynamic dispatch and flexible object structure make this pattern a natural fit, avoiding the need for complex interfaces like in statically typed languages.
# frozen_string_literal: true
class Entry
def size
raise NotImplementedError, "Subclasses must implement the 'size' method"
end
end
class File < Entry
def initialize(name, size)
@name = name
@size = size
end
def size
@size
end
def to_s
"File: #{@name} (#{@size} bytes)"
end
end
class Directory < Entry
def initialize(name)
@name = name
@entries = []
end
def add(entry)
@entries << entry
end
def size
@entries.sum(&:size)
end
def to_s
"Directory: #{@name}\n#{@entries.map { |e| " #{e}" }.join("\n")}"
end
end
# Example Usage
root = Directory.new("root")
doc = Directory.new("documents")
root.add(doc)
file1 = File.new("report.txt", 1024)
file2 = File.new("notes.md", 512)
doc.add(file1)
doc.add(file2)
image = File.new("image.jpg", 2048)
root.add(image)
puts root
puts "Total size: #{root.size} bytes"
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It’s useful for representing hierarchical structures like file systems, organizational charts, or UI elements. In this example, we represent a graphical shape hierarchy where shapes can be primitives (like Circle or Square) or containers (CompositeShape) holding other shapes. The Shape protocol defines a common area() method, enabling consistent calculation even with nested shapes. Swift’s protocol-oriented programming and optional children make it a natural fit for this pattern, avoiding deep class hierarchies. The use of var for children makes the composition mutable, allowing shapes to be added/removed dynamically.
// Shape Protocol
protocol Shape {
func area() -> Double
}
// Primitive Shapes
struct Circle: Shape {
let radius: Double
func area() -> Double {
return .pi * radius * radius
}
}
struct Square: Shape {
let sideLength: Double
func area() -> Double {
return sideLength * sideLength
}
}
// Composite Shape
class CompositeShape: Shape {
private var children: [Shape] = []
func add(shape: Shape) {
children.append(shape)
}
func remove(shape: Shape) {
children.removeAll { $0 as AnyObject === shape as AnyObject }
}
func getChild(index: Int) -> Shape? {
return children[index] as? Shape
}
func area() -> Double {
return children.reduce(0) { $0 + $1.area() }
}
}
// Example Usage
let circle = Circle(radius: 5)
let square = Square(sideLength: 4)
let composite = CompositeShape()
composite.add(shape: circle)
composite.add(shape: square)
let anotherCircle = Circle(radius: 2)
composite.add(shape: anotherCircle)
print("Total area: \(composite.area())")
if let firstChild = composite.getChild(index: 0) {
print("First child area: \(firstChild.area())")
}
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It defines a tree-like structure where each node can be either a leaf (representing a simple object) or a composite (representing a collection of objects). This enables operations to be performed on entire structures without knowing whether they are dealing with individual objects or groups of objects.
The Kotlin code defines an Item interface with a getPrice() method. LeafItem represents individual items, while CompositeItem holds a list of Item instances. CompositeItem delegates the getPrice() call to its children, calculating the total cost. This is idiomatic Kotlin due to the use of interfaces for defining component types, data classes for simple items, and list manipulation for composition. The pattern avoids deep inheritance hierarchies and promotes code reuse.
interface Item {
fun getPrice(): Double
}
data class LeafItem(val name: String, val price: Double) : Item {
override fun getPrice(): Double = price
}
class CompositeItem(val name: String) : Item {
private val children = mutableListOf<Item>()
fun add(item: Item) {
children.add(item)
}
fun remove(item: Item) {
children.remove(item)
}
override fun getPrice(): Double = children.sumOf { it.getPrice() }
}
fun main() {
val order = CompositeItem("Order")
val book1 = LeafItem("Kotlin in Action", 49.99)
val book2 = LeafItem("Programming Kotlin", 39.99)
val package1 = CompositeItem("Book Package")
package1.add(book1)
package1.add(book2)
val shipping = LeafItem("Shipping", 5.00)
order.add(package1)
order.add(shipping)
println("Total order price: ${order.getPrice()}")
}
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It’s useful when you have hierarchical structures and want to perform operations on them without knowing whether you’re dealing with a leaf or a composite node. This Rust example represents a file system where files and directories can both implement a common FileSystemNode trait. The Directory struct acts as the composite, holding a Vec of FileSystemNodes. The File struct represents a leaf. The size method is implemented on the trait allowing a uniform way to calculate the size contributed by either a file or a directory (and recursively all files within a directory). Rust’s traits and ownership system naturally lend themselves to this pattern.
use std::fmt;
// Component
trait FileSystemNode {
fn size(&self) -> u64;
fn name(&self) -> &str;
}
// Leaf
struct File {
name: String,
size: u64,
}
impl File {
fn new(name: String, size: u64) -> Self {
File { name, size }
}
}
impl FileSystemNode {
fn size(&self) -> u64 {
self.size
}
fn name(&self) -> &str {
&self.name
}
}
// Composite
struct Directory {
name: String,
children: Vec<Box<dyn FileSystemNode>>,
}
impl Directory {
fn new(name: String) -> Self {
Directory {
name,
children: Vec::new(),
}
}
fn add(&mut self, node: Box<dyn FileSystemNode>) {
self.children.push(node);
}
}
impl FileSystemNode {
fn size(&self) -> u64 {
let mut total_size = 0;
if let Some(dir) = self as *const Self as *const Directory {
unsafe {
for child in &(*dir).children {
total_size += child.size();
}
}
}
total_size
}
fn name(&self) -> &str {
if let Some(dir) = self as *const Self as *const Directory {
unsafe {
&(*dir).name
}
} else {
"" // Should not occur, handled by the File struct
}
}
}
fn main() {
let mut root = Directory::new("root".to_string());
let dir1 = Directory::new("dir1".to_string());
let file1 = File::new("file1.txt".to_string(), 1024);
let file2 = File::new("file2.txt".to_string(), 2048);
dir1.add(Box::new(file1));
dir1.add(Box::new(file2));
root.add(Box::new(dir1));
println!("Size of root directory: {} bytes", root.size());
}
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It’s used when you have hierarchical relationships where you want to perform operations on both individual “leaves” and complex “branches” in the same way. In this Go example, we model a file system. Entry is the component interface, implemented by both File (leaf) and Directory (composite). Directory contains a list of Entry interfaces, allowing it to represent a hierarchy. The Name() and Size() methods are defined on the interface and are implemented by each concrete type, demonstrating the uniform treatment of files and directories. This approach leans on interfaces, a core part of Go’s type system, to achieve polymorphism and adheres to Go’s composition-over-inheritance philosophy.
package main
import "fmt"
// Entry is the component interface
type Entry interface {
Name() string
Size() int
}
// File represents a leaf node in the file system
type File struct {
name string
size int
}
func (f *File) Name() string {
return f.name
}
func (f *File) Size() int {
return f.size
}
// Directory represents a composite node in the file system
type Directory struct {
name string
entries []Entry
}
func (d *Directory) Name() string {
return d.name
}
func (d *Directory) Size() int {
totalSize := 0
for _, entry := range d.entries {
totalSize += entry.Size()
}
return totalSize
}
func (d *Directory) AddEntry(entry Entry) {
d.entries = append(d.entries, entry)
}
func main() {
root := &Directory{name: "root"}
file1 := &File{name: "file1", size: 10}
file2 := &File{name: "file2", size: 20}
subdir := &Directory{name: "subdir"}
file3 := &File{name: "file3", size: 5}
root.AddEntry(file1)
root.AddEntry(file2)
root.AddEntry(subdir)
subdir.AddEntry(file3)
fmt.Printf("Name: %s, Size: %d\n", root.Name(), root.Size())
fmt.Printf("Name: %s, Size: %d\n", subdir.Name(), subdir.Size())
fmt.Printf("Name: %s, Size: %d\n", file1.Name(), file1.Size())
}
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It defines a tree-like structure where each node can be either a leaf (individual object) or a composite (collection of objects). This example demonstrates it with a simple shape hierarchy. Shapes can be individual circles or rectangles, or a group containing other shapes. All shapes have a draw() method, allowing you to iterate through a composite and draw all contained shapes without knowing their specific types. The C implementation utilizes structs and function pointers to achieve polymorphism. The use of structs is idiomatic for grouping data, and function pointers allow for flexible behavior definitions.
#include <stdio.h>
#include <stdlib.h>
// Define the Shape interface
typedef struct Shape Shape;
typedef void (*DrawFunc)(const Shape*);
struct Shape {
DrawFunc draw;
void* data; // Generic data for each shape type
};
// Concrete Shape: Circle
typedef struct Circle Circle;
struct Circle {
int x, y, radius;
};
void circle_draw(const Shape* shape) {
const Circle* circle = (const Circle*)shape->data;
printf("Drawing Circle at (%d, %d) with radius %d\n", circle->x, circle->y, circle->radius);
}
Shape* create_circle(int x, int y, int radius) {
Circle* circle = (Circle*)malloc(sizeof(Circle));
circle->x = x;
circle->y = y;
circle->radius = radius;
Shape* shape = (Shape*)malloc(sizeof(Shape));
shape->draw = circle_draw;
shape->data = circle;
return shape;
}
// Concrete Shape: Rectangle
typedef struct Rectangle Rectangle;
struct Rectangle {
int x, y, width, height;
};
void rectangle_draw(const Shape* shape) {
const Rectangle* rect = (const Rectangle*)shape->data;
printf("Drawing Rectangle at (%d, %d) with width %d and height %d\n", rect->x, rect->y, rect->width, rect->height);
}
Shape* create_rectangle(int x, int y, int width, int height) {
Rectangle* rect = (Rectangle*)malloc(sizeof(Rectangle));
rect->x = x;
rect->y = y;
rect->width = width;
rect->height = height;
Shape* shape = (Shape*)malloc(sizeof(Shape));
shape->draw = rectangle_draw;
shape->data = rect;
return shape;
}
// Composite Shape: ShapeGroup
typedef struct ShapeGroup ShapeGroup;
struct ShapeGroup {
Shape* children[100]; // Fixed size array for simplicity
int count;
};
void shape_group_draw(const Shape* shape) {
const ShapeGroup* group = (const ShapeGroup*)shape->data;
for (int i = 0; i < group->count; i++) {
group->children[i]->draw(group->children[i]);
}
}
Shape* create_shape_group() {
ShapeGroup* group = (ShapeGroup*)malloc(sizeof(ShapeGroup));
group->count = 0;
Shape* shape = (Shape*)malloc(sizeof(Shape));
shape->draw = shape_group_draw;
shape->data = group;
return shape;
}
void add_shape(Shape* group, Shape* shape) {
ShapeGroup* g = (ShapeGroup*)group->data;
g->children[g->count++] = shape;
}
int main() {
Shape* circle1 = create_circle(10, 20, 5);
Shape* circle2 = create_circle(30, 40, 10);
Shape* rect1 = create_rectangle(5, 5, 15, 20);
Shape* group1 = create_shape_group();
add_shape(group1, circle1);
add_shape(group1, circle2);
Shape* group2 = create_shape_group();
add_shape(group2, rect1);
add_shape(group2, group1);
group2->draw(group2);
// Free allocated memory (important!)
free(circle1->data);
free(circle1);
free(circle2->data);
free(circle2);
free(rect1->data);
free(rect1);
free(group1->data);
free(group1);
free(group2->data);
free(group2);
return 0;
}
The Composite pattern treats individual objects and compositions of objects uniformly. It defines hierarchical elements (components) to represent whole-part relationships. The client can interact with these components consistently without knowing whether it’s dealing with a simple element or a complex composition. This example represents a graphical shapes hierarchy where shapes can be composed of other shapes. We define an abstract Shape class, concrete Circle and Square classes implementing the leaf nodes, and a ShapeGroup class representing a composite node. The operation() method (here, area()) is defined in the abstract class and implemented in all concrete classes, enabling uniform interaction with both primitive shapes and groups. This leverages C++’s polymorphism and class hierarchy for a natural and efficient composite structure.
#include <iostream>
#include <vector>
#include <cmath>
// Component
class Shape {
public:
virtual double area() = 0;
virtual ~Shape() = default;
};
// Leaf
class Circle : public Shape {
private:
double radius_;
public:
Circle(double radius) : radius_(radius) {}
double area() override { return M_PI * radius_ * radius_; }
};
// Leaf
class Square : public Shape {
private:
double side_;
public:
Square(double side) : side_(side) {}
double area() override { return side_ * side_; }
};
// Composite
class ShapeGroup : public Shape {
private:
std::vector<Shape*> shapes_;
public:
void add(Shape* shape) { shapes_.push_back(shape); }
void remove(Shape* shape) {
for (size_t i = 0; i < shapes_.size(); ++i) {
if (shapes_[i] == shape) {
shapes_.erase(shapes_.begin() + i);
return;
}
}
}
double area() override {
double total_area = 0.0;
for (Shape* shape : shapes_) {
total_area += shape->area();
}
return total_area;
}
~ShapeGroup() {
for (Shape* shape : shapes_) {
delete shape;
}
}
};
int main() {
Shape* circle = new Circle(5.0);
Shape* square = new Square(4.0);
ShapeGroup* group = new ShapeGroup();
group->add(circle);
group->add(square);
Shape* another_circle = new Circle(2.0);
group->add(another_circle);
std::cout << "Total area: " << group->area() << std::endl;
delete group; // ShapeGroup destructor deletes contained shapes
return 0;
}
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It’s used when you have a hierarchical structure of objects, and you want to perform operations on the entire structure without knowing whether you’re dealing with a single object or a composite of many. In this C# example, we represent a file system structure where files and folders can be nested. The FileSystemComponent is the abstract base class, File is a leaf node representing a file, and Folder is a composite node containing other FileSystemComponents. The Operation() method demonstrates a common operation (printing name) done on the whole structure. This implementation leverages interfaces and abstract classes, common in C# for defining flexible and extensible behaviors.
// FileSystemComponent.cs
using System;
using System.Collections.Generic;
public interface IFileSystemComponent
{
void Operation(int depth);
}
public abstract class FileSystemComponent : IFileSystemComponent
{
protected string name;
public FileSystemComponent(string name)
{
this.name = name;
}
public abstract void Operation(int depth);
}
// File.cs
public class File : FileSystemComponent
{
public File(string name) : base(name) { }
public override void Operation(int depth)
{
Console.WriteLine($"{new string(' ', depth * 2)}- {name}");
}
}
// Folder.cs
public class Folder : FileSystemComponent
{
private List<IFileSystemComponent> children = new List<IFileSystemComponent>();
public Folder(string name) : base(name) { }
public void Add(IFileSystemComponent component)
{
children.Add(component);
}
public void Remove(IFileSystemComponent component)
{
children.Remove(component);
}
public override void Operation(int depth)
{
Console.WriteLine($"{new string(' ', depth * 2)}+ {name}");
foreach (var child in children)
{
child.Operation(depth + 1);
}
}
}
// Example usage.cs
public class Example
{
public static void Main(string[] args)
{
var root = new Folder("MyDocuments");
root.Add(new Folder("Work"));
root.Add(new File("Resume.txt"));
var workFolder = (Folder)root.children[0];
workFolder.Add(new File("ProjectReport.docx"));
workFolder.Add(new Folder("ClientA"));
var clientAFolder = (Folder)workFolder.children[1];
clientAFolder.Add(new File("Contract.pdf"));
root.Operation(0);
}
}
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It represents whole-part relationships, where you can build complex structures from simpler ones. In this TypeScript example, we have a Component interface representing the base for both individual and composite elements. Leaf represents individual elements (e.g., a file), and CompositeFolder represents a folder that can contain other components (files or folders). The operation() method is defined in the Component and implemented by both Leaf and CompositeFolder, enabling consistent interaction with the entire structure. Using interfaces and classes is very idiomatic TypeScript, promoting strong typing and maintainability.
// Component interface
interface Component {
name: string;
operation(): void;
}
// Leaf class (individual object)
class File implements Component {
name: string;
constructor(name: string) {
this.name = name;
}
operation(): void {
console.log(`Opening file: ${this.name}`);
}
}
// Composite class (composition of objects)
class CompositeFolder implements Component {
name: string;
children: Component[];
constructor(name: string) {
this.name = name;
this.children = [];
}
add(component: Component): void {
this.children.push(component);
}
remove(component: Component): void {
this.children = this.children.filter(child => child !== component);
}
operation(): void {
console.log(`Opening folder: ${this.name}`);
for (const child of this.children) {
child.operation();
}
}
}
// Example Usage:
const rootFolder = new CompositeFolder("Root");
const folder1 = new CompositeFolder("Folder 1");
const file1 = new File("File 1.txt");
const file2 = new File("File 2.jpg");
folder1.add(file1);
folder1.add(file2);
rootFolder.add(folder1);
rootFolder.add(new File("File 3.pdf"));
rootFolder.operation();
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It’s used when you have a hierarchical structure where you need to perform operations on both individual elements and groups of elements. In this example, we model a file system where files and directories can both have a size() method. Directories contain other files/directories, representing the composite structure. The code uses JavaScript classes to define the component (File and Directory) and the composition (Directory). The consistent interface (size()) enables treating both files and directories interchangeably.
// Component interface
class FileSystemNode {
constructor(name) {
this.name = name;
}
size() {
throw new Error("Method 'size()' must be implemented.");
}
}
// Leaf node
class File extends FileSystemNode {
constructor(name, fileSize) {
super(name);
this.fileSize = fileSize;
}
size() {
return this.fileSize;
}
}
// Composite node
class Directory extends FileSystemNode {
constructor(name) {
super(name);
this.children = [];
}
add(node) {
this.children.push(node);
}
size() {
let totalSize = 0;
for (const child of this.children) {
totalSize += child.size();
}
return totalSize;
}
}
// Example Usage
const root = new Directory("root");
const dir1 = new Directory("dir1");
const file1 = new File("file1.txt", 10);
const file2 = new File("file2.jpg", 100);
dir1.add(file1);
dir1.add(file2);
root.add(dir1);
root.add(new File("file3.pdf", 50));
console.log("Total size:", root.size()); // Output: Total size: 160
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It’s used when you have a hierarchical structure where you want to perform operations on both leaves (individual objects) and composites (groups of objects) in the same way.
This example models a file system. File represents a leaf node and Folder represents a composite node. Both implement a common Component interface with methods like list_contents and get_size. Folder contains a list of other Component objects, enabling us to recursively compute size and list contents. This is idiomatic Python as it uses duck typing – the list_contents method operates on anything that responds to it, without strict type checking. The use of composition over inheritance keeps the structure flexible and allows for easy extension.
from abc import ABC, abstractmethod
class Component(ABC):
@abstractmethod
def list_contents(self):
pass
@abstractmethod
def get_size(self):
pass
class File(Component):
def __init__(self, name, size):
self.name = name
self.size = size
def list_contents(self):
return [self.name]
def get_size(self):
return self.size
class Folder(Component):
def __init__(self, name):
self.name = name
self.children = []
def add(self, component):
self.children.append(component)
def list_contents(self):
contents = [self.name]
for child in self.children:
contents.extend(child.list_contents())
return contents
def get_size(self):
total_size = 0
for child in self.children:
total_size += child.get_size()
return total_size
if __name__ == "__main__":
file1 = File("document.txt", 1024)
file2 = File("image.jpg", 2048)
folder1 = Folder("My Documents")
folder1.add(file1)
folder1.add(file2)
file3 = File("report.pdf", 4096)
folder2 = Folder("Projects")
folder2.add(folder1)
folder2.add(file3)
print(folder2.list_contents())
print(f"Total size: {folder2.get_size()} bytes")
The Composite pattern allows you to treat individual objects and compositions of objects uniformly. It creates a tree-like structure where you can perform operations on both individual ’leaves’ and composite ‘branches’ in the same way. This example demonstrates a file system structure, where files are leaves and directories are composites. We define a common FileSystemComponent interface, and then File and Directory classes implement this interface. Directory contains a list of other FileSystemComponent instances, allowing for hierarchical structures. This implementation uses interfaces and classes, common Java practices, enabling polymorphism and clear separation of concerns.
import java.util.ArrayList;
import java.util.List;
// Component
interface FileSystemComponent {
void print();
double getSize();
}
// Leaf
class File implements FileSystemComponent {
private final String name;
private final double size;
public File(String name, double size) {
this.name = name;
this.size = size;
}
@Override
public void print() {
System.out.println(" File: " + name + " (" + size + " KB)");
}
@Override
public double getSize() {
return size;
}
}
// Composite
class Directory implements FileSystemComponent {
private final String name;
private final List<FileSystemComponent> children = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void add(FileSystemComponent component) {
children.add(component);
}
@Override
public void print() {
System.out.println("Directory: " + name);
for (FileSystemComponent child : children) {
child.print();
}
}
@Override
public double getSize() {
double totalSize = 0;
for (FileSystemComponent child : children) {
totalSize += child.getSize();
}
return totalSize;
}
}
public class CompositePattern {
public static void main(String[] args) {
Directory root = new Directory("Root");
Directory documents = new Directory("Documents");
Directory images = new Directory("Images");
root.add(documents);
root.add(images);
documents.add(new File("report.docx", 1024));
documents.add(new File("presentation.pptx", 2048));
images.add(new File("photo.jpg", 512));
images.add(new Directory("Screenshots"));
images.add(new File("logo.png", 256));
root.print();
System.out.println("Total size: " + root.getSize() + " KB");
}
}