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

Structure

Class diagram (UML):

Composite Class Diagram (UML)

Object diagram (UML):

Composite Object Diagram (UML)

When Would You Use It?

  • You want to have abstract tree representation of objects (part-whole hierarchies).
  • You want clients to treat all objects in the composite structure and individual objects uniformly.

Discussion

Where to do “Chid Management”?

The “better” (well, still in debating) place is to put them in “Component” (the common interface) although Leaf actually does not need those methods.

The main reason is the core purpose of “Composite” – to allow client to work with an object of Leaf or Composite uniformly. We need to ensure the client does not need to tell which kinds of nodes they are dealing with at runtime and to expose the internal details of the composite structure to clients as well.

With this decision, for those “Child Management” methods, it is better to provide some “default” implementation (return null) so that Leaf does not need to do anything when inheriting it. In order words, the “Component” would be better done as “Abstract Class” not “Interface“.

References

  • Design Patterns: Elements of Reusable Object-Oriented Software, by Book by Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm (GoF)
  • Pro Objective-C Design Patterns for iOS, by Carlo Chung
  • Pro Design Patterns in Swift, by Adam Freeman

Example #1 – Graphic Marks

Here is the hierarchy of graphic mark:

  • A “mark” can be either a “dingle dot” or a “stroke”.
  • A “stroke” contains multiple “dots”.

Here is the class diagram:

Composite Class Diagram (UML) - Graphic Mark

Code

import UIKit


// Abstrac class for "Component"
class Mark {

    // properties
    var color: UIColor?
    var size: CGSize?
    var location: CGPoint?
    weak var parent: Mark? // have a "parent" reference for better traverse. Use "weak" reference.

    // common Methods
    func draw() {

        // default draw()
        println("Default draw.....nothing!")
    }

    // child Management Methods
    func add(mark: Mark) {

        // default add() for "leaf"
        // basically do "nothing"
        println("A leaf cannot add any child.")
    }

    func remove(index: Int) {

        // default remove() for "leaf"
        // basically do "nothing"
        println("A leaf cannot remove any child.")
    }

    func getDot(index: Int) -> Mark? {

        // default remove() for "leaf"
        // basically do "nothing"
        println("A leaf cannot get any child.")
        return nil
    }

}


// Class for "Leaf" 

class Dot: Mark {

    // initializer
    init(color: UIColor, size: CGSize, location: CGPoint) {

        super.init()

        self.color = color
        self.size = size
        self.location = location
    }

    // common Methods
    override func draw() {

        // draw() for "leaf"
        println("A dot is drawn at location: (location!) with the color: (color!) and size: (size!).")
    }

}

// Class for "Composite" 

class Stroke: Mark {

    // properties owned by "Composite" 
    var dots: [Mark]

    // initializer
    init(_ dots: Mark...) {
        self.dots = dots
        super.init()
    }

    // common Methods
    override func draw() {

        // loop into all children and call draw() individually.
        for dot in dots {
            dot.draw()
        }
    }

    // child Management Methods
    override func add(mark: Mark) {

        mark.parent = self
        dots.append(mark)
        println("A composite adds a new child, with the parent: (mark.parent!).")
    }

    override func remove(index: Int) {

        dots.removeAtIndex(index)
        println("A composite removes the #(index+1) child.")
    }

    override func getDot(index: Int) -> Mark? {

        println("A composite returns the #(index+1) child.")
        return dots[index]
    }

}

// Usage

let dotOrigin = Dot(color: UIColor.redColor(), size: CGSize(width: 2, height: 2), location: CGPoint(x: 0, y: 0))
let dotStart = Dot(color: UIColor.blueColor(), size: CGSize(width: 2, height: 2), location: CGPoint(x: 5, y: 5))
let dotMiddle = Dot(color: UIColor.blueColor(), size: CGSize(width: 2, height: 2), location: CGPoint(x: 8, y: 8))
let dotEnd = Dot(color: UIColor.blueColor(), size: CGSize(width: 2, height: 2), location: CGPoint(x: 10, y: 10))

let strokeLinear = Stroke(dotStart, dotMiddle)
let strokeOverall = Stroke(strokeLinear, dotOrigin)

dotOrigin.draw()
strokeLinear.draw()

strokeLinear.add(dotEnd)
strokeLinear
strokeLinear.getDot(2)
strokeLinear.remove(2)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>