Le motif composite

Définition

Ce motif permet de représenter des objets selon une hiérarchie composant/composé. C’est donc typiquement une représentation en arbre, avec ses branches et ses feuilles. Elle permet de mettre une couche d’abstraction entre le client et les éléments qu’il manipule, cela que ces éléments soient des composants ou des composés.

Principe

Pour réaliser ce motif, il faut une structure commune : une série d’opération que pourront effectuer aussi bien les composants que les composés. C’est une manière de faire abstraction des différences entre deux types d’objet (l’un qui compose l’autre) et donc d’avoir une arboresence d’objet, une grappe.

L’exemple par le code

Pour illustrer ce motif, j’ai repris l’exemple simple utilisé sur ruby garden [en] en une version légèrement adaptée.

Voici donc le premier élément, qui est un point commun au couple composant/composé.

 class Component
   def initialize(name="", description="")
     @name = name
     @description = description
   end
   def to_s(indent=0)
     ("-" * indent) + "#{@name} : #{@description}" 
   end
 end

Voici ensuite le composé en lui-même.

 class Composite < Component
   def to_s(indent=0)
     s = super(indent)
     if defined? @children
       @children.each do |child|
         s += "\n#{child.to_s(indent+1)}" 
       end
     end
     s
   end
   def add(component)
     @children = [] if not defined? @children
     @children << component
   end
 end

Et enfin le composant.

 class Leaf < Component
   def initialize(name="", description="", action="")
     @name = name
     @description = description
     @action = action
   end
   def to_s(indent=0)
     s = super(indent)
     s += " [#{@action}]" 
   end
 end

Simple non ?

Voyons maintenant comment tester tout ça.

 main_menu = Composite.new("Start","menu principal")
 term = Leaf.new("xterm", "terminal", "xterm")
 firefox = Leaf.new("Firefox", "Navigateur", "mozilla-firefox")
 multimedia_menu = Composite.new("Multimedia", "Les sons, les fims")

 multimedia_menu.add(Leaf.new("Sound Juicer", "Pour mettre en Ogg","sound-juicer"))
 multimedia_menu.add(Leaf.new("xmms","Pour écouter les Ogg","xmms"))
 main_menu.add(term)
 main_menu.add(firefox)
 main_menu.add(multimedia_menu)

 puts main_menu.to_s()

Ce qui nous affiche :

 Start : menu principal
 -xterm : terminal [xterm]
 -Firefox : Navigateur [mozilla-firefox]
 -Multimedia : Les sons, les fims
 --Sound Juicer : Pour mettre en Ogg [sound-juicer]
 --xmms : Pour écouter les Ogg [xmms]

Il est important de noter que ce motif s’associe très bien avec le motif iterator. Ici, c’est l’appel à la méthode each qui fait office d’itération, mais pour des objets plus complexes, nous pourrions très bien faire appel à des itérateurs externes aux composants/composés et itérer ainsi sur tout type d’objet.