Design Patterns in Golang: Composite

Introduction

A Composite Design Pattern is a structural pattern that uses to employ composition when implementing an interface rather than using multiple inheritance. It composes objects into tree structures and lets clients treat individual objects and compositions uniformly.

There are situations when clients ignore the difference between compositions of objects and individual objects. If dealing with multiple objects in the same way as handle each of deam is identical, the Composite Design pattern is a good choice to decrease the complexity and treat them as homogeneous.

Purpose

Design Pattern Diagram

The Composite Pattern provides the following units: Component, Leaf and Composite.

Composite Class Diagram

Implementation

The Composite Design Pattern is very common approach for implementing a document object model hierarchy. Such an example are image editors, which compose different shapes and layers into hierarchy.

Lets implement a basic architecture of such an editor. Lets use the following interface:

// VisualElement that is drawn on the screen
type VisualElement interface {
	// Draw draws the visual element
	Draw(drawer *Drawer) error
}

The editor supports two kind of shapes (circle and square). Each of the structs that represents the coresponding shape obeys the VisualElement interface by implementing a Draw function that has exactly the same signiture exposed in the interface. The following code snippet illustrate the implementation of thoes components:

// Square represents a square
type Square struct {
	// Location of the square
	Location Point
	// Side size
	Side float64
}

// Draw draws a square
func (square *Square) Draw(drawer *Drawer) error {
	return drawer.DrawRect(Rect{
		Location: square.Location,
		Size: Size{
			Height: square.Side,
			Width:  square.Side,
		},
	})
}

// Circle represents a circle shape
type Circle struct {
	// Center of the circle
	Center Point
	// Radius of the circle
	Radius float64
}

// Draw draws a circle
func (circle *Circle) Draw(drawer *Drawer) error {
	rect := Rect{
		Location: Point{
			X: circle.Center.X - circle.Radius,
			Y: circle.Center.Y - circle.Radius,
		},
		Size: Size{
			Width:  2 * circle.Radius,
			Height: 2 * circle.Radius,
		},
	}

	return drawer.DrawEllipseInRect(rect)
}

In order to allow composition and drawing of multiple shapes on the screen, a Layer compose thoes object. It contains an array of VisualElement. It is responsible to interate over the elements and draw each of them. As you can see the actual struct uses the VisualElement interface as a contract to support different shapes no matter what is their type.

// Layer contains composition of visual elements
type Layer struct {
	// Elements of visual elements
	Elements []VisualElement
}

// Draw draws a layer
func (layer *Layer) Draw(drawer *Drawer) error {
	for _, element := range layer.Elements {
		if err := element.Draw(drawer); err != nil {
			return err
		}
		fmt.Println()
	}

	return nil
}

The object can be composed as it is shown in the following code snippet:

circle := &photoshop.Circle{
	Center: photoshop.Point{X: 100, Y: 100},
	Radius: 50,
}

square := &photoshop.Square{
	Location: photoshop.Point{X: 50, Y: 50},
	Side:     20,
}

layer := &photoshop.Layer{
	Elements: []photoshop.VisualElement{
		circle,
		square,
	},
}

layer.Draw(&photoshop.Drawer{})

Verdict

Dealing with Tree-structured data makes code more complex, and therefore, error prone. The Composite Design Patterns provides a solution that allows treating complex and primitive objects uniformly. The operations you can perform on all the composite objects often have a least common relationship that allows handling a set of object as a single unit.