Fabrique abstraite

{{#ifeq:||Un article de Ziki, l'encyclopédie libre.|Une page de Ziki, l'encyclopédie libre.}}

La fabrique abstraite est un patron de conception (design pattern) créationnel utilisé en génie logiciel orienté objet.

Elle fournit une interface pour créer des familles d'objets liés ou inter-dépendants sans avoir à préciser au moment de leur création la classe concrète à utiliser<ref>Modèle:Ouvrage</ref>.

Principe

Une fabrique abstraite encapsule un ensemble de fabriques ayant une thématique commune. Le code client crée une instance concrète de la fabrique abstraite, puis utilise son interface générique pour créer des objets concrets de la thématique. Le client ne se préoccupe pas de savoir quelle fabrique crée un objet concret, ni de quelle classe concrète est l'objet en question : il n'utilise que les interfaces génériques des objets produits<ref>Modèle:Lien web</ref>. Ce patron de conception sépare donc les détails d'implémentation d'un ensemble d'objets de leur usage générique.

Un exemple de fabrique abstraite : la classe DocumentCreator fournit une interface permettant de créer différents produits (e.g. createLetter() et createResume()). Le système a à sa disposition des versions concrètes dérivées de la classe DocumentCreator, comme fancyDocumentCreator et modernDocumentCreator, qui possèdent chacune leur propre implémentation de createLetter() et createResume() pouvant créer des objets tels que fancyLetter ou modernResume. Chacun de ces produits dérive d'une classe abstraite simple comme Letter ou Resume, connues du client. Le code client obtient une instance de DocumentCreator qui correspond à sa demande, puis appelle ses méthodes de fabrication. Tous les objets sont créés par une implémentation de la classe commune DocumentCreator et ont donc la même thématique (ici, ils seront tous fancy ou modern). Le client a seulement besoin de savoir manipuler les classes abstraites Letter ou Resume, et non chaque version particulière obtenue de la fabrique concrète.

Une fabrique est un endroit du code où sont construits des objets. Le but de ce patron de conception est d'isoler la création des objets de leur utilisation. On peut ainsi ajouter de nouveaux objets dérivés sans modifier le code qui utilise l'objet de base.

Avec ce patron de conception, on peut interchanger des classes concrètes sans changer le code qui les utilise. Toutefois, il exige un travail supplémentaire lors de la conception et du développement initial, et apporte une certaine complexité.

Utilisation

La fabrique détermine le type de l'objet concret qu'il faut créer, et c'est ici que l'objet est effectivement créé (dans le cas du C++, c'est l'instruction new). Cependant, la fabrique retourne un pointeur abstrait sur l'objet concret créé.

Le code client est ainsi isolé de la création de l'objet en l'obligeant à demander à une fabrique de créer l'objet du type abstrait désiré et de lui en retourner le pointeur.

Comme la fabrique retourne uniquement un pointeur abstrait, le code client qui sollicite la fabrique ne connaît pas et n'a pas besoin de connaître le type concret précis de l'objet qui vient d'être créé. Cela signifie en particulier que :

  • Le code client n'a aucune connaissance du type concret, et ne nécessite donc aucun fichier header ou déclaration de classe requis par le type concret. Le code client n'interagit qu'avec la classe abstraite. Les objets concrets sont en effet créés par la fabrique, et le code client ne les manipule qu'avec leur interface abstraite.
  • L'ajout de nouveaux types concrets dans le code client se fait en spécifiant l'utilisation d'une fabrique différente, modification qui concerne typiquement une seule ligne de code (une nouvelle fabrique crée des objets de types concrets différents, mais renvoie un pointeur du même type abstrait, évitant ainsi de modifier le code client). C'est beaucoup plus simple que de modifier chaque création de l'objet dans le code client. Si toutes les fabriques sont stockées de manière globale dans un singleton et que tout le code client utilise ce singleton pour accéder aux fabriques pour la création d'objets, alors modifier les fabriques revient simplement à modifier l'objet singleton.

Structure

Diagramme de classe UML du patron de fabrique abstraite.

Exemples

Dans l'exemple ci-dessous, implémenté en divers langages, la classe Application crée un bouton sans savoir s'il s'agit d'une IHM Windows ou OSX. Par suite, ce bouton est manipulé (paint), toujours indépendamment du type d'interface.

C++

Modèle:Boîte déroulante/début <syntaxhighlight lang="cpp">

/* exemple d'une fabrique abstraite d'éléments d'IHM en C++ */

struct Button {

 virtual void paint() = 0;

};

class WinButton : public Button {

 void paint (){
   std::cout << " I'm a window button \n";
 }

};

class OSXButton : public Button {

 void paint (){
   std::cout << " I'm a OSX button \n";
 }

};

struct GUIFactory {

 virtual Button* createButton () = 0;

};

class WinGUIFactory : public GUIFactory {

 Button* createButton (){
   return new WinButton();
 }

};

class OSXGUIFactory : public GUIFactory {

 Button* createButton (){
   return new OSXButton();
 }

};

class Application { public:

   Application(GUIFactory* factory){
       Button * button = factory->createButton();
       button->paint();
       delete button;
   }

};

/* application : */

int main() {

 GUIFactory* factory;
  1. if defined(_WIN32)
 factory = new WinGUIFactory();
  1. elif defined(__APPLE__)
 factory = new OSXGUIFactory();
  1. endif
 Application* app = new Application (factory);

 delete factory;
 delete app;

 return 0;

} </syntaxhighlight>Modèle:Boîte déroulante/fin

C#

Modèle:Boîte déroulante/début <syntaxhighlight lang="csharp"> /*

* GUIFactory example
*/

abstract class GUIFactory {
    public static GUIFactory getFactory() {
        int sys = readFromConfigFile("OS_TYPE");
        if (sys==0) {
            return(new WinFactory());
        } else {
            return(new OSXFactory());
        }
   }
   public abstract Button createButton();
}

class WinFactory:GUIFactory {
    public override Button createButton() {
        return(new WinButton());
    }
}

class OSXFactory:GUIFactory {
    public override Button createButton() {
        return(new OSXButton());
    }
}

abstract class Button  {
    public string caption;
    public abstract void paint();
}

class WinButton:Button {
    public override void paint() {
       Console.WriteLine("I'm a WinButton: "+caption);
    }
}

class OSXButton:Button {
    public override void paint() {
       Console.WriteLine("I'm a OSXButton: "+caption);
    }
}

class Application {
    static void Main(string[] args) {
        GUIFactory aFactory = GUIFactory.getFactory();
        Button aButton = aFactory.createButton();
        aButton.caption = "Play";
        aButton.paint();
    }
    //output is
    //I'm a WinButton: Play
    //or
    //I'm a OSXButton: Play
}

</syntaxhighlight>Modèle:Boîte déroulante/fin

VB.Net

Modèle:Boîte déroulante/début <syntaxhighlight lang="vbnet"> ' ' * GUIFactory example ' MustInherit Class GUIFactory

   Public Shared Function getFactory() As GUIFactory
       Dim sys As Integer = readFromConfigFile("OS_TYPE")
       If sys = 0 Then
           Return (New WinFactory())
       Else
           Return (New OSXFactory())
       End If
   End Function
   Public MustOverride Function createButton() As Button

End Class Class WinFactory

   Inherits GUIFactory
   Public Overloads Overrides Function createButton() As Button
       Return (New WinButton())
   End Function

End Class Class OSXFactory

   Inherits GUIFactory
   Public Overloads Overrides Function createButton() As Button
       Return (New OSXButton())
   End Function

End Class MustInherit Class Button

   Public caption As String
   Public MustOverride Sub paint()

End Class Class WinButton

   Inherits Button
   Public Overloads Overrides Sub paint()
       Console.WriteLine("I'm a WinButton: " & caption)
   End Sub

End Class Class OSXButton

   Inherits Button
   Public Overloads Overrides Sub paint()
       Console.WriteLine("I'm a OSXButton: " & caption)
   End Sub

End Class Class Application

   Private Shared Sub Main(ByVal args As String())
       Dim aFactory As GUIFactory = GUIFactory.getFactory()
       Dim aButton As Button = aFactory.createButton()
       aButton.caption = "Play"
       aButton.paint()
   End Sub
   'output is
   'I'm a WinButton: Play
   'or
   'I'm a OSXButton: Play

End Class </syntaxhighlight>Modèle:Boîte déroulante/fin

Java

Modèle:Boîte déroulante/début <syntaxhighlight lang="java">

/*
* GUIFactory Example
*/

public abstract class GUIFactory
{
   public static GUIFactory getFactory()
   {
        int sys = readFromConfigFile("OS_TYPE");
        if (sys == 0)
            return(new WinFactory());
        return(new OSXFactory());
   }
   public abstract Button createButton();
}

class WinFactory extends GUIFactory
{
    public Button createButton()
    {
        return(new WinButton());
    }
}

class OSXFactory extends GUIFactory
{
    public Button createButton()
    {
        return(new OSXButton());
    }
}

public abstract class Button
{
    private String caption;
    public String getCaption()
    {
        return caption;
    }
    public void setCaption(String caption)
    {
        this.caption = caption;
    }
    public abstract void paint();
}

class WinButton extends Button
{
    public void paint()
    {
        System.out.println("I'm a WinButton: "+ getCaption());
    }
}

class OSXButton extends Button
{
    public void paint()
    {
        System.out.println("I'm an OSXButton: "+ getCaption());
    }
}

public class Application
{
    public static void main(String[] args)
    {
        GUIFactory aFactory = GUIFactory.getFactory();
        Button aButton = aFactory.createButton();
        aButton.setCaption("Play");
        aButton.paint();
    }
    //output is
    //I'm a WinButton: Play
    //or
    //I'm a OSXButton: Play
}

</syntaxhighlight>Modèle:Boîte déroulante/fin

Perl

Modèle:Boîte déroulante/début <syntaxhighlight lang="perl">

# GUIFactory example on Perl

package GUIFactory;

sub getFactory($$) {
        shift; # skip class
        my $toolkit = shift;
        if ($toolkit eq 'GTK') {
            return(GtkFactory->new);
        } else {
            return(TkFactory->new);
        }
}

package GtkFactory;
use base 'GUIFactory';
 
sub new {
    bless({}, shift);
}

sub createButton {
        return(GtkButton->new);
}

package TkFactory;
use base 'GUIFactory';

sub new {
        bless({}, shift);
}

sub createButton() {
        return(TkButton->new);
}

package Button;

sub new {
        $class = shift;
        my $self = {};
        $self{caption} = ;
        bless($self, $class);
        return $self;
}

package GtkButton;
use base 'Button';

sub paint() {
       print "I'm a GtkButton\n";
}

package TkButton;
use base 'Button';

sub paint() {
       print "I'm a TkButton\n";
}

package main;

my $aFactory = GUIFactory->getFactory;
my $aButton = $aFactory->createButton;
$aButton->{caption} = "Play";
$aButton->paint();

</syntaxhighlight>Modèle:Boîte déroulante/fin

PHP

Modèle:Boîte déroulante/début <syntaxhighlight lang="php"> /*

* Fabrique abstraite
*/

abstract class GUIFactory {

   public static function getFactory() {
       $sys = readFromConfigFile("OS_TYPE");
       if ($sys == 0) {
           return(new WinFactory());
       } else {
           return(new OSXFactory());
       }
   }
   public abstract function createButton(): Button;

}

class WinFactory extends GUIFactory {

   public function createButton(): Button {
       return(new WinButton());
   }

}

class OSXFactory extends GUIFactory {

   public function createButton(): Button {
       return(new OSXButton());
   }

}

abstract class Button {

   private $_caption;
   public abstract function render();
    
   public function getCaption(){
       return $this->_caption;
   }
   public function setCaption($caption){
       $this->_caption = $caption;
   }

}

class WinButton extends Button {

   public function render() {
       return "Je suis un WinButton: ".$this->getCaption();
   }

}

class OSXButton extends Button {

   public function render() {
       return "Je suis un OSXButton: ".$this->getCaption();
   }

}

$aFactory = GUIFactory::getFactory(); $aButton = $aFactory->createButton(); $aButton->setCaption("Démarrage"); echo $aButton->render();

//Le rendu est //Je suis un WinButton: Démarrage //ou //Je suis un OSXButton: Démarrage </syntaxhighlight>Modèle:Boîte déroulante/fin

Eiffel

Modèle:Boîte déroulante/début <syntaxhighlight lang="eiffel">

--
-- GUI Factory example;
--
class GUI_FACTORY_FOR_CONFIG feature
   factory: GUI_FACTORY
      once
         inspect read_from_config_file("OS_TYPE")
         when 0 then
            create {WIN_FACTORY} Result
         else
            create {OSX_FACTORY} Result
         end
      end
end
deferred class GUI_FACTORY feature
   create_button: BUTTON
       deferred end
end
class WIN_FACTORY inherit GUI_FACTORY feature
   create_button: WIN_BUTTON
       do
           create Result
       end
end
class OSX_FACTORY inherit GUI_FACTORY feature
   create_button: OSX_BUTTON
       do
           create Result
       end
end
deferred class BUTTON feature
   caption: STRING
   
   set_caption (a_caption: like caption) 
       do
           caption := a_caption
       end
   
   paint
       deferred end
end
class WIN_BUTTON inherit BUTTON feature
   paint
       do
           print ("I'm a WIN_BUTTON: "+caption+"%N")
       end
end

class OSX_BUTTON inherit BUTTON feature
   paint
       do
           print ("I'm a OSX_BUTTON: "+caption+"%N")
       end
end

class
   APPLICATION
inherit
   GUI_FACTORY_FOR_CONFIG
create
   make
feature
   make
       local
           button: BUTTON
       do
           button := factory.create_button
           button.set_caption ("Play")
           button.paint
       end
end

</syntaxhighlight>Modèle:Boîte déroulante/fin

Python

Modèle:Boîte déroulante/début <syntaxhighlight lang="python">

  1. !/usr/bin/env python
  2. -*- coding: utf-8 -*-
  1. ==============================================================================
  2. Exemple d'une fabrique abstraite d’élément en Python
  3. ==============================================================================

class RaceFactory(object):

   @staticmethod
   def get_factory(race_type):
       if race_type == 'automobile':
           return SpeedwayFactory()
       elif race_type == 'hippique':
           return HippodromeFactory()
       else:
           return StadiumFactory()
   
   def create_racer(self):
       raise NotImplementedError()


class SpeedwayFactory(RaceFactory):

   def create_racer(self):
       return Car()


class HippodromeFactory(RaceFactory):

   def create_racer(self):
       return Horse()


class StadiumFactory(RaceFactory):

   def create_racer(self):
       return Human()


class Racer(object):

   def __init__(self):
       self._nickname = None
   def start(self):
       raise NotImplementedError()
   @property
   def nickname(self):
       return self._nickname
   @nickname.setter
   def nickname(self, nickname):
       self._nickname = nickname


class Car(Racer):

   def start(self):
       return "Vroum vroum (%s)" % self.nickname


class Horse(Racer):

   def start(self):
       return "HiIi hIiI, cataclop (%s)" % self.nickname


class Human(Racer):

   def start(self):
       return "C'est parti ! (%s)" % self.nickname


if __name__ == '__main__':

   import random
   t = random.choice(['automobile', 'hippique', 'marathon'])
   a_factory = RaceFactory.get_factory(t)
   a_racer = a_factory.create_racer()
   a_racer.nickname = "Le Sans-Espoir"
   print(a_racer.start())
   # Affiche 
   # "Vroum vroum (Le Sans-Espoir)"
   # ou
   # "HiIi hIiI, cataclop (Le Sans-Espoir)"
   # ou
   # "C'est parti ! (Le Sans-Espoir)"

</syntaxhighlight>Modèle:Boîte déroulante/fin

Notes et références

<references />

Annexes

Modèle:Autres projets

Articles connexes

Liens externes

Bibliographie


Modèle:Palette Patron de conception

Modèle:Portail