Abstract factory

La programmation avec design patterns

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Une bonne ambiance dans une famille est souvent synonyme d'une séparation décisionnelle. Cela veut dire que les décisions sont prises séparément, sans aucune intervention de grand-parents ou des cousins. Cette illustration trouve également son utilité dans le monde de développement des applications web, et plus particulièrement dans la fabrique abstraite.

Ce nouveau design pattern, appelé abstract factory (fabrique abstraite), permet de gérer des familles d'objets. Pour faciliter la compréhension, il est souvent comparé à une "fabrique des fabriques", c'est-à-dire une classe implémentant le patron de conception factory pouvant créer d'autres classes construites selon le même design pattern.

Afin de mieux comprendre ceci, revenons à notre exemple de l'introduction. Une famille est composée de grands-parents, cousins, cousines, tantes etc. Ce que nous intéresse, c'est la décisions d'une famille au sujet de lieu de vacances. Une autre famille a le même problème. Sauf que la décision des deux ne peut pas être liée. Du coup, les deux produisent le même type de décision (lieu de vacances), mais le font indepéndamment, en fonction de leurs critères. Et alors une famille peut prendre cette décision selon le budget tandis que l'autre peut le faire en analysant la météo. La fabrique abstraite dans cet exemple se trovue au niveau de la décision. Il s'agit d'une notion commune, mais gérée des 2 manières différentes, mais par les mêmes acteurs (grands-parents, cousins, tantes etc.).

On peut en déduire que la fabrique abstraite répond bien au besoins de modularisation des applications web. Le code métier est alors séparé du produit final. Elle permet de créer de familles d'objets sans spécifier explicitement les classes qui vont être créées. Ces classes seront alors séparées du client, c'est-à-dire du code qui demande leur initialisation.

Voici les acteurs qui participent dans le développement d'une fabrique abstraite :
- abstract factory (fabrique abstraite) : la classe qui définit la création d'une famille d'objets
- abstract product (produit abstrait) : la classe qui définit un des composants de la fabrique abstraite.
- product (produit) : la classe qui initialise un des composants de la fabrique abstraite.
- client : le consommateur de la fabrique abstraite et du produit abstrait.

Dans la meilleure compréhension de ce design pattern on utilisera l'exemple d'une tactique pour l'équipe de football. On aura deux tactiques : offensive (4 défenseurs, 3 milieux de terrain et 3 attaquants) et défensive (5 défenseurs, 4 milieux de terrain et 1 attaquant). Les deux seront créées en fonction du code passe à une méthode d'initialisation (433 pour offensive, 541 pour défensive).

Exemple abstract factory

abstract class Defense{
public abstract String getNr();
}

abstract class Middlefield{
public abstract String getNr();
}

abstract class Striker{
public abstract String getNr();
}

Ces 3 classes représentent : les défenseurs, les milieux de terrain et les attaquants. Elles correspondent à l'acteur appelé produit abstrait. Ce sont elles qui vont composer la fabrique abstraite (tactique). La méthode getNr() retournera le nombre de joueurs pour une formation tactique donnée.


class OffensiveDefense extends Defense{
@Override
public String getNr(){
return "4";
}
}

class OffensiveMiddlefield extends Middlefield{
@Override
public String getNr(){
return "3";
}
}

class OffensiveStriker extends Striker{
@Override
public String getNr(){
return "3";
}
}

class DefensiveDefense extends Defense{
@Override
public String getNr(){
return "5";
}
}

class DefensiveMiddlefield extends Middlefield{
@Override
public String getNr(){
return "4";
}
}

class DefensiveStriker extends Striker{
@Override
public String getNr(){
return "1";
}
}

Il s'agit ici des produits. Ils vont composer les fabriques concrètes (tactique offensive et tactique défensive).



abstract class AbstractTactic{
public abstract Defense makeDefense();

public abstract Middlefield makeMiddlefields();

public abstract Striker makeStrikers();
}

C'est notre fabrique abstraite. On voit la présence des 3 méthodes qui font lien avec les 3 produits abstraits mentionnés.


class OffensiveTactic extends AbstractTactic{
@Override
public Defense makeDefense(){
return new OffensiveDefense();
}

@Override
public Middlefield makeMiddlefields(){
return new OffensiveMiddlefield();
}

@Override
public Striker makeStrikers(){
return new OffensiveStriker();
}
}

class DefensiveTactic extends AbstractTactic{
@Override
public Defense makeDefense(){
return new DefensiveDefense();
}

@Override
public Middlefield makeMiddlefields(){
return new DefensiveMiddlefield();
}

@Override
public Striker makeStrikers(){
return new DefensiveStriker();
}
}

On voit ici la fabrique concrète pour chacune des tactiques. On remarque que la création des joueurs se fait d'une manière indépendante.


class TeamFactory{
private static AbstractTactic tactic;

public static AbstractTactic getTactic(final String code) throws Exception{
if(tactic!=null) return tactic;
else if(code.equals("433")){
tactic=new OffensiveTactic();
}
else if(code.equals("451")){
tactic=new DefensiveTactic();
}
else throw new Exception("Unknown tactic");
return tactic;
}
}

Ceci est notre code qui décide quelle fabrique concrète doit être utilisée.



final String[] tactics=new String[] {"433", "451"};
for(final String tc : tactics){
try{
final AbstractTactic tactic=TeamFactory.getTactic(tc);
final Defense dc=tactic.makeDefense();
final Middlefield mc=tactic.makeMiddlefields();
final Striker st=tactic.makeStrikers();
System.out.println(tc+" is composed by :");
System.out.println(dc.getNr()+" - "+mc.getNr()+" - "+st.getNr());
System.out.println("----------------");
}
catch(final Exception e){
e.printStackTrace();
}
}

Ce code client présente comment utiliser la fabrique abstraite. On voit que cet usage est généraliste. Il n'y a aucun code spécifique à une tactive offensive ou défensive. Et plus bas, le résultat de ce code :


433 is composed by :
4 - 3 - 3
----------------
451 is composed by :
4 - 3 - 3
----------------


La fabrique abstraite est un design pattern qui permet de mieux modulariser le code. Le découpage du client des produits fait en sorte que de nouvelles fabriques peuvent être rajoutées très facilement. Puisque le client manipule toujours sur les types abstraits, l'ajout d'une nouvelle famille d'objets se fera par une simple définition d'une nouvelle fabrique et ses produits. Ce patron de conception s'adapte donc parfaitement aux applications qui nécessitent la gestion de plusieurs familles d'objets. Il peut être aussi utile dans le scas où un groupe de produits est censé de marcher ensemble au sein d'une même structure.
Bartosz KONIECZNY 06-10-2013 16:25 design patterns
Moi

Développeur d'applications Internet et journaliste passionné par l'adjectif français. Un aigle polonais orienté vers la progression, volant très haut et écoutant du zouk après les matches du foot français.

Vous appréciez mon travail ?

Pour contribuer au développement de ce site, ou pour remercier pour des articles rédigés, vous pouvez faire un don.

Un conseil Symfony2

Un problème avec la définition des valeurs par défaut pour input type checkbox ?

Si vous rencontrez un problème avec la définition des valeurs par défaut pour un champ du type checkbox sous Symfony2, assurez-vous de la conformité des types de ces valeurs. Par exemple, le code suivant ne va pas fonctionner (le checkboxes ne seront pas sélectionnés pour les valeurs indiquées) :

  private $gifts = array(1 => 'apple', 2 => 'orange');
  public function setPreferedGifts($value = array())
  {
    $vals = array();
    foreach($value as $v => $val)
    {
      $vals[] = $val;
    }
    $this->preferedGifts = $vals;
  }
Par contre, le code suivant fonctionnera correctement (les checkboxes seront sélectionnés pour des valeurs passées dans la boucle foreach) :
  private $gifts = array(1 => 'apple', 2 => 'orange');
  public function setPreferedGifts($value = array())
  {
    $vals = array();
    foreach($value as $v => $val)
    {
      $vals[] = (int)$val;
    }
    $this->preferedGifts = $vals;
  }
Pour résumer, si le tableau avec les choix ($gifts dans l'exemple) contient les clés qui sont des integers, les valeurs par défaut doivent aussi être des integers.