Le Design Pattern Poids Mouche (Flyweight) en PHP

Suite à la rédaction d’un billet sur le sujet par Jean-François LÉPINE, j’ai décidé de publier plus vite que prévu l’article sur le modèle de conception « Poids Mouche » pour re-préciser son utilisation et sa définition.

Modèle du Poids Mouche

Qu’est-ce que le Poids mouche, quel est son objectif ?
L’objectif du poids mouche est de réduire la consommation mémoire d’un programme, en partageant des instances d’un même objet (souvent des « Value Objects ») fréquemment utilisés.

Exemple d’implémentation

Nous avons un objet de type ParagraphText qui peut être écrit de plusieurs couleurs différentes : TextColor.
TextColor est fortement sollicité et coûte énormément de mémoire, ainsi, il sera la cible de notre Poids Mouche.

class ParagraphText
{
  private $_text;
  private $_color;

  public function __construct ($text, TextColor $color)
  {
     $this->_text = $text;
     $this->_color = $color;
  }
}

class TextColor
{
   private $_binary_data_at_least_1GB;
   public function construct ($binary_data_at_least_1GB)
   {
      $this->_binary_data_at_least_1GB = $binary_data_at_least_1GB;
   }
}

Maintenant, voyons sans FlyWeight ce que cela pourrait donner :

//récupération des données grâce à un iterateur
$text = array();
foreach ($textDataIterator as $textData){
   $text[] = new ParagraphText(
                              $textData->text,
                              new TextColor($textData->oh_my_god_this_is_huge_data)
                              );
}

Cela va donc instancier pour chaque texte énormément d’objets « couleurs », quand bien même chaque texte serait écrit en noir.

L’idée du FlyWeight est donc de limiter les objets TextColor par…. couleur.

On peut passer pour ce faire par une Fabrique

class FlyWeightTextColorFactory
{
   private static $_colors = array();
   public static function create($binary_data_at_least_1GB)
   {
      if (array_key_exists($hashValue = self::_hash($binary_data_at_least_1GB), self::$_colors)){
         return self::$_colors[$hashValue];
      } else {
         return self::$_colors[$hashValue] = new TextColor($binary_data_at_least_1GB);
      }
   }
}

Et adapter notre client ainsi :

//récupération des données grâce à un iterateur
$text = array();
foreach ($textDataIterator as $textData){
   $text[] = new ParagraphText(
                              $textData->text,
                              FlyWeightTextColorFactory::create($textData->oh_my_god_this_is_huge_data)
                              );
}

Dès lors, les couleurs ne seront instanciées qu’une seule fois…. et ne consommeront que le strict nécessaire en mémoire.

  1. Exemple d’incompréhension patternique | Gege2061's blog - pingback on 9 juin 2011 at 13 h 01 min
  2. Merci pour cet éclaircissement.
    La description dans le premier billet ressemblait à un gros raccourci.

    Par contre, ne devrait-on pas écrire « poidS mouche » peut-être ?

  3. Merci d’avoir pris le temps de traiter sérieusement le sujet ;)

  4. Juste pour chipoter :

    1/ je comprend pas vraiment ce qu’est $binary_data_at_least_1GB ? C’est censé représenter la définition d’une couleur ?

    2/ Sous toute réserve, il me semble que tant qu’une donnée n’est pas modifiée, alors elle n’est pas réellement copié dans son espace mémoire. Ainsi, je ne suis pas sûr que :
    $this->_binary_data_at_least_1GB = $binary_data_at_least_1GB;
    prend plus de temps que la copie d’une référence.

    Et du coup, si ce n’est pas le cas, n’est-il pas plus malin d’utiliser une référence lorsque l’on a des $binary_data_at_least_1GB ?

    • 1) Oui, c’est vrai que l’exagération était peut être un peu poussée dans l’exemple :-)

      2) J’avoue que je n’ai jamais benché la différence de rapidité entre copie de référence et copie par valeur (d’autant plus avec le fait que PHP, à ma connaissance, ne copie effectivement la donnée que si elle diffère / au moment ou elle diffère)

    • Si les données proviennent de sources différentes (plusieurs résultats d’une requête SQL), elles sont dupliquées par PHP.

      Ce pattern peut être intéressant dans le cas d’une requête SQL avec jointure justement.

      SELECT post.id, post.name, category.id, category.name FROM post JOIN category.

      Ce pattern peut être utilisé pour créer des objets category partagés entre les posts.

  5. ah ouais un ServiceLocator quoi

    • Non, on ne peut ici parler de Service Locator.

      L’objectif du Service Locator est simplement de distribuer l’implémentation d’une interface (masquant la propagation de dépendances que l’on pourrait constater en utilisant directement des fabriques dédiées).

      Souvent un Service Locator est un Registre avec des méthodes permettant de récupérer les dits services.

      Ici, le Poids Mouche distribue des Value Object, non des Services.

      Pour être exhaustif, je rajoute à ma TODO la création d’un billet sur le sujet.

  6. Je crois que en Java ça s’appel un ResourceLocator ? Mais j’aime bien Poids Mouche :lol:

  7. Votre blog est vraiment très intéressant. En lisant plusieurs de vos articles, je me suis dit « hey en fait, le multiton c’est un flyweight sur la class elle même… :???: « . Et donc je voulais savoir si je me trompais ou non. :?:

    Par avance Merci, et bonne continuation.

Laisser un commentaire


NOTE - Vous pouvez utiliser les éléments et attributs HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Trackbacks and Pingbacks: