582 votes

Quelles sont les différences entre les patrons de conception Abstract Factory et Factory ?

Je sais qu'il existe de nombreux articles sur les différences entre ces deux modèles, mais il y a quelques éléments que je ne trouve pas.

D'après ce que j'ai lu, je vois que le modèle de méthode factory permet de définir comment créer un seul produit concret, tout en cachant l'implémentation au client, qui verra un produit générique. Ma première question concerne l'usine abstraite. Son rôle est-il de vous permettre de créer des familles d'objets concrets (qui peuvent dépendre de la fabrique spécifique que vous utilisez) plutôt qu'un seul objet concret ? La fabrique abstraite ne renvoie-t-elle qu'un seul très gros objet ou plusieurs objets en fonction des méthodes que vous appelez ?

Mes deux dernières questions portent sur une seule citation que je n'arrive pas à comprendre entièrement et que j'ai vue à de nombreux endroits :

Une différence entre les deux est que avec le modèle Abstract Factory, une classe délègue la responsabilité de l'instanciation d'un objet à un autre objet via la composition, alors que le modèle Factory Method utilise l'héritage et s'appuie sur une sous-classe pour gérer la l'instanciation de l'objet souhaité.

Si j'ai bien compris, le modèle de méthode d'usine possède une interface Creator qui fait que le ConcreteCreator est chargé de savoir quel ConcreteProduct instancier. Est-ce cela que signifie l'utilisation de l'héritage pour gérer l'instanciation des objets ?

Maintenant, en ce qui concerne cette citation, comment exactement le modèle Abstract Factory délègue-t-il la responsabilité de l'instanciation de l'objet à un autre objet via la composition ? Qu'est-ce que cela signifie ? À mes yeux, il semble que le modèle Abstract Factory utilise également l'héritage pour effectuer le processus de construction, mais là encore, je suis toujours en train d'apprendre à connaître ces modèles.

Toute aide, en particulier pour la dernière question, serait grandement appréciée.

4 votes

0 votes

Voir "comment l'instance a été créée" du point de vue du client, vous aiderait à comprendre le devis.

4 votes

@nawfal, les réponses dans ce fil sont affreuses.

3voto

BuckCherry Points 919

Comprenez les différences dans les motivations :

Supposons que vous construisiez un outil dans lequel vous avez des objets et une implémentation concrète des interrelations entre les objets. Comme vous prévoyez des variations dans les objets, vous avez créé une indirection en assignant la responsabilité de créer des variantes des objets à un autre objet ( nous l'appelons l'usine abstraite ). Cette abstraction est très avantageuse car vous prévoyez que de futures extensions auront besoin de variantes de ces objets.

Une autre motivation assez intrigante dans cette ligne de pensée est le cas où tous les objets du groupe entier, ou aucun d'entre eux, auront une variante correspondante. En fonction de certaines conditions, l'une ou l'autre des variantes sera utilisée et, dans chaque cas, tous les objets doivent être de la même variante. Cela peut être un peu contre intuitif à comprendre car nous avons souvent tendance à penser que - tant que les variantes d'un objet suivent un contrat uniforme commun ( interface au sens large ), le code d'implémentation concret ne devrait jamais se casser. Ce qui intrigue ici, c'est que ce n'est pas toujours vrai, notamment lorsque le comportement attendu ne peut être modélisé par un contrat de programmation.

Un simple ( en empruntant l'idée à GoF ) est une application GUI qui émule le look-an-feel des OS MS, Mac ou Fedora. Ici, par exemple, lorsque tous les objets widgets tels que les fenêtres, les boutons, etc. ont une variante MS, à l'exception de la barre de défilement qui est dérivée de la variante MAC, l'objectif de l'outil échoue gravement.

Les cas ci-dessus constituent le besoin fondamental de Modèle de fabrique abstraite .

D'un autre côté, imaginez que vous écrivez un framework pour que de nombreuses personnes puissent construire divers outils ( comme celui des exemples ci-dessus ) en utilisant votre cadre. Par l'idée même d'un framework, vous n'avez pas besoin de le faire, bien que vous ne puissiez pas utiliser d'objets concrets dans votre logique. Vous mettez plutôt en place des contrats de haut niveau entre les différents objets et leur interaction. Pendant que vous ( en tant que développeur de cadres ) restent à un niveau très abstrait, chaque constructeur de l'outil est obligé de suivre vos structures. Cependant, ils ( les constructeurs d'outils ) ont la liberté de décider de l'objet à construire et de la manière dont tous les objets qu'ils créent vont interagir. Contrairement au cas précédent ( du modèle de fabrique abstraite ), vous ( en tant que créateur de cadre ) n'ont pas besoin de travailler avec des objets concrets dans ce cas, et peuvent plutôt rester au niveau du contrat des objets. De plus, contrairement à la deuxième partie des motivations précédentes, vous ou les constructeurs d'outils ne serez jamais confrontés à la situation de mélanger des objets provenant de variantes. Ici, alors que le code du cadre reste au niveau du contrat, chaque constructeur d'outils est limité ( par la nature même de l'affaire ) à l'utilisation de leurs propres objets. Dans ce cas, la création d'objets est déléguée à chaque implémenteur et les fournisseurs de frameworks se contentent de fournir des méthodes uniformes pour créer et renvoyer des objets. Ces méthodes sont inévitables pour les développeurs de frameworks afin de poursuivre leur code et ont un nom spécial appelé Méthode d'usine ( Modèle de méthode d'usine pour le modèle sous-jacent ).

Quelques notes :

  • Si vous êtes familier avec la notion de "méthode template", vous verrez que les méthodes d'usine sont souvent invoquées à partir de méthodes template dans le cas de programmes appartenant à toute forme de framework. En revanche, les méthodes de modèle des programmes d'application sont souvent une simple mise en œuvre d'un algorithme spécifique et sont dépourvues de méthodes d'usine.
  • De plus, pour l'exhaustivité de la réflexion, en utilisant le cadre ( mentionné ci-dessus ), lorsqu'un constructeur d'outils construit un outil, à l'intérieur de chaque méthode de fabrique, au lieu de créer un objet concret, il peut déléguer la responsabilité à un objet de fabrique abstrait, à condition que le constructeur d'outils prévoie des variations des objets concrets pour les extensions futures.

Code échantillon :

//Part of framework-code
BoardGame {
    Board createBoard() //factory method. Default implementation can be provided as well
    Piece createPiece() //factory method

    startGame(){        //template method
         Board borad = createBoard()
         Piece piece = createPiece()
         initState(board, piece)
    }
}

//Part of Tool-builder code
Ludo inherits  BoardGame {
     Board createBoard(){ //overriding of factory method
         //Option A: return new LudoBoard() //Lodu knows object creation
         //Option B: return LudoFactory.createBoard() //Lodu asks AbstractFacory
     }
….
}

//Part of Tool-builder code
Chess inherits  BoardGame {
    Board createBoard(){ //overriding of factory method
        //return a Chess board
    }
    ….
}

2voto

Adrian Liu Points 251

Précisons que la plupart du temps, dans le code de production, nous utilisons le modèle d'usine abstrait parce que la classe A est programmée avec l'interface B. Et A a besoin de créer des instances de B. Donc A doit avoir un objet usine pour produire des instances de B. Donc A ne dépend d'aucune instance concrète de B. J'espère que cela vous aidera.

2voto

yoAlex5 Points 2350
generation 1 <- generation 2 <- generation 3
//example
shape <- rectangle, oval <- rectangle impressionism, rectangle surrealism, oval impressionism, oval surrealism

Usine

Cas d'utilisation : instanciation un objet de generation 2

Il s'agit d'un Creational qui vous permet de créer generation 2 dans un endroit simple. Il est conforme à SRP et OCP - tous les changements sont effectués dans une seule classe.

enum ShapeType {
    RECTANGLE,
    OVAL
}

class Shape {}

//Concrete Products
//generation 2
class Rectangle extends Shape {}
class Oval extends Shape {}

//Factory
class Factory {
    Shape createShape(ShapeType type) {

        switch (type) {
            case RECTANGLE:
                return new Rectangle();
            case OVAL:
                return new Oval();
        }
    }
}

//Creator
class Painter {

    private Factory factory;

    Painter(Factory factory) {
        this.factory = factory;
    }

    Shape prepareShape(ShapeType type) {
        return factory.createShape(type);
    }
}

//using
class Main {
    void main() {
        Painter painter = new Painter(new ShapeFactory());

        Shape shape1 = painter.prepareShape(ShapeType.RECTANGLE);
        Shape shape2 = painter.prepareShape(ShapeType.OVAL);
    }
}

Méthode d'usine

Cas d'utilisation : instanciation un objet de generation 3

Aide à travailler avec la prochaine génération de membres de la famille. Chaque peintre a son propre style comme l'impressionnisme, le surréalisme... Factory Method utilise l'abstrait Creator comme Factory(méthode abstraite) et Concrete Creators sont des réalisations de cette méthode

enum ShapeType {
    RECTANGLE,
    OVAL
}

class Shape {}

//Concrete Products
//generation 2
class Rectangle extends Shape {}
class Oval extends Shape {}

//generation 3
class RectangleImpressionism extends Rectangle {}
class OvalImpressionism extends Oval {}
class RectangleSurrealism extends Rectangle {}
class OvalSurrealism extends Oval {}

//Creator
abstract class Painter {

    Shape prepareShape(ShapeType type) {
        return createShape(type);
    }

    //Factory method
    abstract Shape createShape(ShapeType type);
}

//Concrete Creators
class PainterImpressionism {

    @override
    Shape createShape(ShapeType type) {
        switch (type) {
            case RECTANGLE:
                return new RectangleImpressionism();
            case OVAL:
                return new OvalImpressionism();
        }
    }
}

class PainterSurrealism {

    @override
    Shape createShape(ShapeType type) {
        switch (type) {
            case RECTANGLE:
                return new RectangleSurrealism();
            case OVAL:
                return new OvalSurrealism();
        }
    }
}

//using
class Main {
    void main() {
        Painter painterImpressionism = new PainterImpressionism();
        Shape shape1 = painterImpressionism.prepareShape(ShapeType.RECTANGLE);

        Painter painterSurrealism = new PainterSurrealism();
        Shape shape2 = painterSurrealism.prepareShape(ShapeType.RECTANGLE);
    }
}

Usine abstraite

Cas d'utilisation : instanciation tous objets de generation 3

Factory fait partie de l'abstrait Factory et les réalisations en Concrete Factories

//Concrete Products
//generation 2
class Rectangle extends Shape {}
class Oval extends Shape {}

//generation 3
class RectangleImpressionism extends Rectangle {}
class OvalImpressionism extends Oval {}
class RectangleSurrealism extends Rectangle {}
class OvalSurrealism extends Oval {}

//Abstract Factory
interface Factory {
    Rectangle createRectangle();
    Oval createOval();
}

//Concrete Factories
class ImpressionismFactory implements Factory {
    @Override
    public Rectangle createRectangle() {
        return new RectangleImpressionism();
    }

    @Override
    public Oval createOval() {
        return new OvalImpressionism();
    }
}

class SurrealismFactory implements Factory {
    @Override
    public Rectangle createRectangle() {
        return new RectangleSurrealism();
    }

    @Override
    public Oval createOval() {
        return new OvalSurrealism();
    }
}

//Creator
class Painter {

    Rectangle rectangle;
    Oval oval;

    Painter(Factory factory) {
        rectangle = factory.createRectangle();
        rectangle.resize();

        oval = factory.createOval();
        oval.resize();
    }
}

//using
class Main {
    void main() {
        Painter painter1 = new Painter(new ImpressionismFactory());
        Shape shape1 = painter1.rectangle;
        Shape shape2 = painter1.oval;

        Painter painter2 = new Painter(new ImpressionismFactory());
        Shape shape3 = painter2.rectangle;
        Shape shape4 = painter1.oval;
    }
}

1voto

Saurabh Points 1015

Pour le rendre très simple avec une interface minimale & veuillez vous concentrer sur "//1" :

class FactoryProgram
    {
        static void Main()
        {
            object myType = Program.MyFactory("byte");
            Console.WriteLine(myType.GetType().Name);

            myType = Program.MyFactory("float"); //3
            Console.WriteLine(myType.GetType().Name);

            Console.ReadKey();
        }

        static object MyFactory(string typeName)
        {
            object desiredType = null; //1
            switch (typeName)
            {
                case "byte": desiredType = new System.Byte(); break; //2
                case "long": desiredType = new System.Int64(); break;
                case "float": desiredType = new System.Single(); break;
                default: throw new System.NotImplementedException();
            }
            return desiredType;
        }
    }

Voici les points importants : 1. Les mécanismes Factory & AbstractFactory doivent utiliser l'héritage (System.Object-> byte, float ...) ; donc si vous avez l'héritage dans le programme alors Factory(Abstract Factory ne serait pas là très probablement) est déjà là par conception 2. Le créateur (MyFactory) connaît le type concret et renvoie donc un objet de type concret à l'appelant (Main) ; dans une usine abstraite, le type de retour serait une interface.

interface IVehicle { string VehicleName { get; set; } }
interface IVehicleFactory
    {
        IVehicle CreateSingleVehicle(string vehicleType);
    }
class HondaFactory : IVehicleFactory
    {
        public IVehicle CreateSingleVehicle(string vehicleType)
        {
            switch (vehicleType)
            {
                case "Sports": return new SportsBike();
                case "Regular":return new RegularBike();
                default: throw new ApplicationException(string.Format("Vehicle '{0}' cannot be created", vehicleType));
            }
        }
    }
class HeroFactory : IVehicleFactory
    {
        public IVehicle CreateSingleVehicle(string vehicleType)
        {
            switch (vehicleType)
            {
                case "Sports":  return new SportsBike();
                case "Scooty": return new Scooty();
                case "DarkHorse":return new DarkHorseBike();
                default: throw new ApplicationException(string.Format("Vehicle '{0}' cannot be created", vehicleType));
            }
        }
    }

class RegularBike : IVehicle { public string VehicleName { get { return "Regular Bike- Name"; } set { VehicleName = value; } } }
class SportsBike : IVehicle { public string VehicleName { get { return "Sports Bike- Name"; } set { VehicleName = value; } } }
class RegularScooter : IVehicle { public string VehicleName { get { return "Regular Scooter- Name"; } set { VehicleName = value; } } }
class Scooty : IVehicle { public string VehicleName { get { return "Scooty- Name"; } set { VehicleName = value; } } }
class DarkHorseBike : IVehicle { public string VehicleName { get { return "DarkHorse Bike- Name"; } set { VehicleName = value; } } }

class Program
{
    static void Main(string[] args)
    {
        IVehicleFactory honda = new HondaFactory(); //1
        RegularBike hondaRegularBike = (RegularBike)honda.CreateSingleVehicle("Regular"); //2
        SportsBike hondaSportsBike = (SportsBike)honda.CreateSingleVehicle("Sports");
        Console.WriteLine("******* Honda **********"+hondaRegularBike.VehicleName+ hondaSportsBike.VehicleName);

        IVehicleFactory hero = new HeroFactory();
        DarkHorseBike heroDarkHorseBike = (DarkHorseBike)hero.CreateSingleVehicle("DarkHorse");
        SportsBike heroSportsBike = (SportsBike)hero.CreateSingleVehicle("Sports");
        Scooty heroScooty = (Scooty)hero.CreateSingleVehicle("Scooty");
        Console.WriteLine("******* Hero **********"+heroDarkHorseBike.VehicleName + heroScooty.VehicleName+ heroSportsBike.VehicleName);

        Console.ReadKey();
    }
}

Points importants : 1. Exigence : Honda créerait "Regular", "Sports" mais Hero créerait "DarkHorse", "Sports" et "Scooty". 2. Pourquoi deux interfaces ? Une pour le type de fabricant (IVehicleFactory) et une autre pour la fabrique du produit (IVehicle) ; l'autre façon de comprendre les 2 interfaces est la fabrique abstraite est tout au sujet de la création d'objets liés 2. Le problème est que les enfants de IVehicleFactory renvoient un IVehicle (au lieu d'un objet concret dans l'usine) ; donc j'obtiens une variable parent (IVehicle) ; puis je crée un type concret réel en appelant CreateSingleVehicle et ensuite je fais un casting de l'objet parent vers l'objet enfant réel. Que se passe-t-il si je fais RegularBike heroRegularBike = (RegularBike)hero.CreateSingleVehicle("Regular"); Vous obtiendrez une ApplicationException et c'est pourquoi nous avons besoin d'une fabrique abstraite générique que je peux expliquer si nécessaire. J'espère que cela vous aidera, que vous soyez débutant ou intermédiaire.

0voto

he9lin Points 21

Je préférerais toujours l'Abstract Factory à la Factory Method. A partir de l'exemple de Tom Dalling (excellente explication) ci-dessus, nous pouvons voir que l'Abstract Factory est plus composable dans la mesure où tout ce que nous avons à faire est de passer une Factory différente au constructeur (injection de dépendance de constructeur en cours ici). Mais la méthode Factory nous oblige à introduire une nouvelle classe (plus de choses à gérer) et à utiliser des sous-classes. Préférez toujours la composition à l'héritage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X