DEV Community

Cover image for Composite (Object Tree)
AK
AK

Posted on • Updated on

Composite (Object Tree)

What is Composite design pattern?

Composite is a structural design pattern that lets you compose objects into tree structures and then work with these structures as if they were individual objects.

The composite pattern is part of the classic [Gang of Four] structural pattern group. GoF authors say about the composite pattern,

Compose objects into tree structure to represent part-whole hierarchies. Composite lets client treat individual objects and compositions of objects uniformly.


Participants of the Composite Pattern

When dealing with Tree-structured data, programmers often have to discriminate between a leaf-node and a branch. This makes code more complex, and therefore, error prone. The solution is an interface that allows treating complex and primitive objects uniformly.

In object-oriented programming, a composite is an object designed as a composition of one-or-more similar objects, all exhibiting similar functionality. This is known as a “has-a” relationship between objects.

The key concept is that you can manipulate a single instance of the object just as you would manipulate a group of them. The operations you can perform on all the composite objects often have a least common denominator relationship.

  1. Component
    Component declares the abstract class for objects in the tree structure. It also implements default behavior for the interface common to all classes as appropriate.

  2. Leaf
    Leaf is a class that extends Component to represent leaves in the tree structure that does not have any children.
    Leaf defines behavior for primitive objects in the composition. It represents leaf objects in the composition. A leaf is an object that doesn't have children.

  3. Composite
    Leaf is a class that extends Component to represent nodes(contain children) in the tree structure.
    Composite stores child components and implements child related operations in the component interface.

  4. Client
    Client manipulates the objects in the composition through the component interface.


Tree structure

Tree structure

The above figure shows a typical Composite object structure. As you can see, there can be many children to a single parent i.e Composite, but only one parent per child.


UML Diagram for the Composite Design Pattern

This is class hierarchy structure

UML Diagram for the Composite Design Pattern


When to use Composite Design Pattern?

  1. When you have to implement a tree-like object structure
    The Composite pattern provides you with two basic element types that share a common interface: simple leaves and complex containers. A container can be composed of both leaves and other containers.

  2. When you want the client code to treat both simple and complex elements uniformly
    All elements defined by the Composite pattern share a common interface.

Composite Pattern should be used when clients need to ignore the difference between compositions of objects and individual objects. If programmers find that they are using multiple objects in the same way, and often have nearly identical code to handle each of them, then composite is a good choice, it is less complex in this situation to treat primitives and composites as homogeneous.

Less number of objects reduces the memory usage, and it manages to keep us away from errors related to memory like java.lang.OutOfMemoryError.

Although creating an object in Java is really fast, we can still reduce the execution time of our program by sharing objects.


When not to use Composite Design Pattern?

It might be difficult to provide a common interface for classes whose functionality differs too much. In certain scenarios, you'd need to overgeneralize the component interface, making it harder to comprehend.

Composite Design Pattern makes it harder to restrict the type of components of a composite. So it should not be used when you don’t want to represent a full or partial hierarchy of objects.
Composite Design Pattern can make the design overly general. It makes harder to restrict the components of a composite.

Sometimes you want a composite to have only certain components. With Composite, you can’t rely on the type system to enforce those constraints for you. Instead you’ll have to use run-time checks.


Code Examples

fs/mod.rs

mod file;
mod folder;

pub use file::File;
pub use folder::Folder;

pub trait Component {
    fn search(&self, keyword: &str);
}
Enter fullscreen mode Exit fullscreen mode

fs/file.rs

use super::Component;

pub struct File {
    name: &'static str,
}

impl File {
    pub fn new(name: &'static str) -> Self {
        Self { name }
    }
}

impl Component for File {
    fn search(&self, keyword: &str) {
        println!("Searching for keyword {} in file {}", keyword, self.name);
    }
}
Enter fullscreen mode Exit fullscreen mode

fs/folder.rs

use super::Component;

pub struct Folder {
    name: &'static str,
    components: Vec<Box<dyn Component>>,
}

impl Folder {
    pub fn new(name: &'static str) -> Self {
        Self {
            name,
            components: vec![],
        }
    }

    pub fn add(&mut self, component: impl Component + 'static) {
        self.components.push(Box::new(component));
    }
}

impl Component for Folder {
    fn search(&self, keyword: &str) {
        println!(
            "Searching recursively for keyword {} in folder {}",
            keyword, self.name
        );

        for component in self.components.iter() {
            component.search(keyword);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

main.rs

mod fs;

use fs::{Component, File, Folder};

fn main() {
    let file1 = File::new("File 1");
    let file2 = File::new("File 2");
    let file3 = File::new("File 3");

    let mut folder1 = Folder::new("Folder 1");
    folder1.add(file1);

    let mut folder2 = Folder::new("Folder 2");
    folder2.add(file2);
    folder2.add(file3);
    folder2.add(folder1);

    folder2.search("rose");
}
Enter fullscreen mode Exit fullscreen mode

References

Top comments (0)