menu
Behavioralarrow_forward_ios
Chain of Responsibility
device_hubComposite

Description

Composite is a structural design pattern that allows works with tree structures objects.

Real World Example

Let’s assume that we have a lot of teams in our company. Teams are broken down into departments and they are further broken down into even smaller divisions. This is an example of a company structure.
|__ Company
    |__ Development
    |   |__ Design
    |   |   |__ UX/UI
    |   |   |__ Graphic
    |   |
    |   |__ FrontEnd
    |   |__ Backend
    |
    |__ Sales
To use the Composite pattern, we need to distinguish three types of classes:
  • Component-base class that defines a common interface for all components. In this case it will be Team
  • Composite-a higher order component that is built from smaller components, e.g. a Design team
  • Leaf-low-order component that does not break into smaller parts, e.g. Graphic team
In our case, the component will be the Team class, which contains the name, the unassigned parent and the number of employees set to 0 by default.

class Team
  attr_accessor :name, :parent

  def initialize(name)
    @name = name
    @parent = nil
  end

  def employees
    0
  end
end
Now we want to build the lowest order class based on the Team class.

class GraphicTeam < Team
  def initialize
    super('Graphic')
  end

  def employees
    4
  end
end
To build a team composed of other teams, we need to build a composite class, which will be responsible for managing the components.

class CompositeTeam < Team
  def initialize(name)
    super(name)
    @sub_teams = []
  end

  def add_sub_team(team)
    @sub_teams << team
    team.parent = self
  end

  def remove_sub_team(team)
    @sub_team.delete(team)
    team.parent = nil
  end

  def employees
    @sub_teams.sum(&:employees)
  end
end
Having a composite class, we are already able to build more complex structures.

class DesignTeam < CompositeTeam
  def initialize
    super('Design')
    add_sub_team(Graphic.new)
    add_sub_team(UXUI.new)
  end
end

class DevelopmentTeam < CompositeTeam
  def initialize
    super('Development')
    add_sub_team(Design.new)
    add_sub_team(Frontend.new)
    add_sub_team(Backend.new)
  end
end

Coding Problem

First, we will create a simple User class that has two attributes, first_name and last_name, and a full_name method that returns the full name of the user.

class User
  attr_reader :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end
  
  def full_name
    "#{first_name} #{last_name}"
  end
end
Next, we will create a Greeter class, with a hello method that will allow us to greet the user.

class Greeter
  def self.hello(user)
    puts "Hello, #{user.full_name}!"
  end
end

mike = User.new('Michael', 'Scott')
Greeter.hello(mike) # => Hello, Michael Scott!
As we can see, everything in the example above works fine. Now let’s assume that we also want to say hello to every team. So let’s create a Team class.

class Team
  attr_reader :name

  def initialize(name)
    @name = name
  end
end
  
development = Team.new('Development')
Greeter.hello(development) # NoMethodError: undefined method 'full_name'
The method will of course raise an error because the Team class doesn’t have a method that returns the full name.

Solution

Before we create a solution to this problem, we need to define three objects that we need in the Adapter pattern.
  • Target-Before we create a solution to this problem, we need to define three objects that we need in the Adapter pattern.
  • Adaptee-The object we want to adapt to work with the target. In this case, it will be the Team class.
  • Adapter-An object that allows Adaptee to connect to a Target.
If we have already defined Target and Adaptee objects, we can prepare our Adapter class. To create it, just wrap Adaptee(Team) with a new class(Team Adapter) and then create a method(full_name) that will be compatible with the Greeter class interface.

class TeamAdapter
  attr_reader :team
  
  def initialize(team)
    @team = team
  end
  
  def full_name
    "#{team.name} Team"
  end
end
An adapter written in such a way can easily be used to work with the Greeter class.

dev_adapter = TeamAdapter.new(development)
Greeter.hello(dev_adapter) # => "Hello, Development Team!"