Composite design pattern in Java and Python

Structural Design Patterns provide different ways to create a class structure, for example using inheritance and composition to create a large object from small objects. Following design patterns come under this category.

Composite pattern lets clients to treat the individual objects in a uniform manner.

The Composite Pattern allows you to compose objects into a tree structure to represent the part-whole hierarchy which means you can create a tree of objects that is made of different parts, but that can be treated as a whole one big thing. Composite lets clients to treat individual objects and compositions of objects uniformly.

Consider for example a program that manipulates a file system. A file system is a tree structure that contains Branches which are Folders as well as Leaf nodes which are Files. Note that a folder object usually contains one or more file or folder objects and thus is a complex object where a file is a simple object. Note also that since files and folders have many operations and attributes in common, such as moving and copying a file or a folder, listing file or folder attributes such as file name and size, it would be easier and more convenient to treat both file and folder objects uniformly by defining a File System Resource Interface.

The figure below shows a UML class diagram for the Composite Pattern:

dp_composite.png

Composite Pattern consists of following objects:

  • Component is the abstraction for leafs and composites. It defines the interface that must be implemented by the objects in the composition. For example a file system resource defines move, copy, rename, and getSize methods for files and folders.
  • Leafs are objects that have no children. They implement services described by the Component interface. For example a file object implements move, copy, rename, as well as getSize methods which are related to the Component interface.
  • A Composite stores child components in addition to implementing methods defined by the component interface. Composites implement methods defined in the Component interface by delegating to child components. In addition composites provide additional methods for adding, removing, as well as getting components.
  • The client manipulates objects in the hierarchy using the component interface.

A client has a reference to a tree data structure and needs to perform operations on all nodes independent of the fact that a node might be a branch or a leaf. The client simply obtains reference to the required node using the component interface, and deals with the node using this interface; it doesn't matter if the node is a composite or a leaf.

When to use Composite Pattern:

  • When you want to represent part-whole hierarchies of objects.
  • When you want clients to be able to ignore the difference between compositions of objects and individual objects. Clients will treat all objects in the composite structure uniformly.

In the composite pattern, a tree structure exists where identical operations can be performed on leaves and nodes. A node in a tree is a class that can have children. A node class is a composite class. A leaf in a tree is a primitive class that does not have children. The children of a composite can be leaves or other composites.

The leaf class and the composite class share a common component interface that defines the common operations that can be performed on leaves and composites. When an operation on a composite is performed, this operation is performed on all children of the composite, whether they are leaves or composites. Thus, the composite pattern can be used to perform common operations on the objects that compose a tree.

Composite Pattern important points

  • Composite pattern should be applied only when the group of objects should behave as the single object.
  • Composite design pattern can be used to create a tree like structure.

Java

Composite pattern component defines the common methods for leaf and composites. We can create a class Shape with a method draw(String fillColor) to draw the shape with given color.

public interface Shape {
    public void draw(String fillColor);
}

Composite design pattern leaf implements component and these are the building block for the composite. We can create multiple leaf objects such as Triangle, Circle etc.

public class Triangle implements Shape {
    @Override
    public void draw(String fillColor) {
        System.out.println("Drawing Triangle with color " + fillColor);
    }
}

public class Circle implements Shape {
    @Override
    public void draw(String fillColor) {
        System.out.println("Drawing Circle with color " + fillColor);
    }
}

A composite object contains group of leaf objects and we should provide some helper methods to add or delete leafs from the group. We can also provide a method to remove all the elements from the group.

import java.util.ArrayList;
import java.util.List;

public class Drawing implements Shape{
    // collection of Shapes
    private List<Shape> shapes = new ArrayList<Shape>();

    @Override
    public void draw(String fillColor) {
        for(Shape sh : shapes) {
            sh.draw(fillColor);
        }
    }

    //adding shape to drawing
    public void add(Shape s){
        this.shapes.add(s);
    }

    //removing shape from drawing
    public void remove(Shape s){
        shapes.remove(s);
    }

    //removing all the shapes
    public void clear(){
        System.out.println("Clearing all the shapes from drawing");
        this.shapes.clear();
    }
}

Notice that composite also implements component and behaves similar to leaf except that it can contain group of leaf elements.

Our composite pattern implementation is ready and we can test it with a client program.

public class TestCompositePattern {
    public static void main(String[] args) {
        Shape tri1 = new Triangle();
        Shape tri2 = new Triangle();
        Shape cir = new Circle();

        Drawing drawing = new Drawing();
        drawing.add(tri1);
        drawing.add(tri2);
        drawing.add(cir);

        drawing.draw("Red");

        drawing.clear();

        drawing.add(tri1);
        drawing.add(cir);
        drawing.draw("Green");
    }

}

Python 3

import abc

class Shape(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def draw(self, color):
        pass

class Triangle(Shape):
    def draw(self, color):
        print("Drawing Triangle with color " + color)

class Circle(Shape):
    def draw(self, color):
        print("Drawing Circle with color " + color)

class Drawing(Shape):
    def __init__(self):
        self.shapes = []

    def draw(self, color):
        for sh in self.shapes:
            sh.draw(color)

    def add(self, sh):
        self.shapes.append(sh)

    def remove(self, sh):
        self.shapes.remove(sh)

    def clear(self):
        print("Clearing all the shapes from drawing")
        self.shapes = []

if __name__ == '__main__':
    tri1 = Triangle()
    tri2 = Triangle()
    cir  = Circle()

    drawing = Drawing()
    drawing.add(tri1)
    drawing.add(tri2)
    drawing.add(cir)

    drawing.draw("Red")

    drawing.clear()

    drawing.add(tri1)
    drawing.add(cir)
    drawing.draw("Green")
comments powered by Disqus