IOC Factory: avantages et inconvénients de L'Interface par rapport aux délégués

N'importe quel endroit où vous avez besoin d'une valeur d'exécution pour construire une dépendance particulière, usine abstraite est la solution.

ma question est: pourquoi de nombreuses sources favorisent-elles FactoryInterface plutôt que FactoryDelegate pour mettre en œuvre Ce modèle? Quels sont les avantages et les inconvénients des deux solutions?

Voici un exemple pour comprendre ce que je veux dire

si vous avez un Service qui a besoin D'un dépôt avec un certain contexte, alors le Service constructeur besoin d'une usine à créer ou accéder à son référentiel.

la solution courante pour cela est de créer une Repoitoryfactoryinterface comme ceci.

public IRepositoryFactory {
    IRepository Create(ContextInformation context);
}

public class MyService {
    private IRepositoryFactory repositoryFactory;
    public MyService(IRepositoryFactory repositoryFactory)
    {
        this.repositoryFactory = repositoryFactory:
    }

    public void DoSomeService()
    {
        ContextInformation context = ....;

        IRepository repository = this.repositoryFactory.Create(context);

        repository.Load(...);
        ...
        repository.Save(...);
    }
}

vous avez également besoin de mettre en œuvre IRepositoryFactory interface d'une façon ou d'une autre

public MyEf4RepositoryFactory : IRepositoryFactory
{
    IRepository Create(ContextInformation context)
    {
        return new MyEf4Repository(context);
    }
}

... et l'utiliser dans l'application

public void main()
{
    IRepositoryFactory repoFactory = new MyEf4RepositoryFactory();
    IService service = new MyService(repoFactory); 

    service.DoSomeService();
}

----- Fin de la solution grand public ------

Au lieu de L'interface de référencement de Factoryinterface vous pouvez faire la même chose avec un factorydelegate qui nécessite moins de codage comme celui-ci.

public class MyService {
    private Func<ContextInformation, IRepository> repositoryFactory;
    public MyService(Func<ContextInformation, IRepository> repositoryFactory)
    {
        this.repositoryFactory = repositoryFactory:
    }

    public void DoSomeService()
    {
        ContextInformation context = ....;

        IRepository repository = this.repositoryFactory(context);

        repository.Load(...);
        ...
        repository.Save(...);
    }
}

... et l'utiliser dans l'application

public void main()
{
    IService service = new MyService(context => new MyEf4Repository(context)); 

    service.DoSomeService();
}

à mon avis, Le délégué de Factor context => new MyEf4Repository(context) est beaucoup plus compact que déclaration et mise en œuvre d'une interface IRepositoryFactory et MyEf4RepositoryFactory .

Il doit y avoir une raison pour cela et je veux savoir pourquoi.

Voici un exemple de source qui utilise l'interface de l'approche: la réponse à est-il-un-modèle-de-l'initialisation des objets créés-par-un-di-container

[mise à jour]15 mois après avoir posé cette question et avoir plus d'expérience avec les java universers, j'ai changé d'avis: Maintenant, je préfère les interfaces aux délégués . Mais je ne peux pas dire pourquoi. C'est juste un sentiment. Peut-être parce que je suis plus habitué à ça?

26
demandé sur Community 2011-03-23 13:50:26

5 réponses

personnellement, j'ai toujours utilisé la solution mainstream, simplement parce que je ne pensais pas à utiliser un délégué.

après y avoir pensé, j'ai fait face au problème de la séparation des préoccupations. J'utilise Ninject, et je ne voulais pas que mon module de reliure ressemble à ça (imaginez que le repositoy ait quelques dépendances de lui-même):

class IoCModule : NinjectModule
{
    public override Load()
    {
        Bind<Func<Context, IRepository>>()
            .ToConstant( context => new MyEf4Repository(context, Kernel.Get<IRepositoryDependency1>, Kernel.Get<IRepositoryDependency2>) );
    }
}

ce n'est pas lisible du tout. Donc j'ai encore utilisé des usines abstraites entièrement tapées pour la séparation des soucis et la lisibilité.

maintenant j'utilise la FuncModule décrite dans cette question (a la AutoFac). Donc je peux faire ceci:

class IoCModule : NinjectModule
{
    public override Load()
    {
        Bind<IRepository>().To<MyEf4Repository>();
        Bind<IRepositoryDependency1>().To<...>();
        Bind<IRepositoryDependency2>().To<...>();
    }
}

et laissez ninject calculer les dépendances pour moi. Comme vous pouvez le voir, il est à la fois plus lisible que d'utiliser la méthode décrite ci-dessus, et d'avoir à lier les usines pour chaque dépendance. C'est ainsi que j'ai fait la transition de la solution mainstream à la solution delegate.

pour répondre à votre question. La raison pour laquelle j'ai utilisé la solution mainstream était parce que je ne savais pas comment le faire d'une autre manière au début (ceci est en partie causé par la plupart des blogs entièrement dactylographiant les usines abstraites, pouvez-vous voir le cercle?).

4
répondu dvdvorle 2017-05-23 12:24:14

tout endroit où vous avez besoin d'une valeur d'exécution pour construire une dépendance particulière, usine abstraite est la solution.

Je argumenterais contre cela. Les dépendances ne devraient pas être construites en utilisant des données d'exécution, comme expliqué ici . En résumé, l'article dit:

construction; cela crée de l'ambiguïté, complique la racine de la composition avec une responsabilité supplémentaire et rend extraordinairement difficile de vérifier l'exactitude de votre configuration DI. Au lieu de cela, laissez les données d'exécution circulent à travers les appels de méthode des graphiques d'objet construits.

quand nous laissons les données d'exécution "circuler à travers les appels de méthode des graphiques d'objets construits" à la place, vous verrez l'utilité des usines abstraites décliner. Ils peut encore être utilisé lorsque les données d'exécution sont utilisées pour choisir des dépendances multiples (par rapport à l'injection de données d'exécution dans une dépendance), mais même alors usines abstraites ne sont généralement pas la meilleure solution, comme expliqué ici . En résumé, l'article dit:

en général, l'utilisation d'une abstraction d'usine n'est pas un dessin ou modèle qui prend en considération ses consommateurs. Selon le principe D'Injection de dépendance (DIP), les abstractions doivent être définies par leurs clients, et puisqu'une usine augmente le nombre de dépendances sur lesquelles un client est forcé de dépendre, l'abstraction n'est clairement pas créée en faveur du client et nous pouvons donc considérer que c'est une violation de la DIP.

au lieu de cela, les modèles tels que la façade, Composite, Médiateur et Proxy sont généralement une meilleure solution.

ça ne veut pas dire toi vous ne pouvez pas avoir de code dans votre application qui produit des dépendances, mais il ne doit pas être défini comme une abstraction qui est utilisée par d'autres composants de l'application. Au lieu de cela, un comportement d'usine devrait être encapsulé dans des adaptateurs qui sont définis comme faisant partie de votre racine de Composition .

quand vous n'avez que ces logiques et dépendances d'usine comme partie de votre racine de Composition, peu importe que vous définissiez un IRepositoryFactory ou simplement utilisez un Func<IRepository> pour construire une telle dépendance, puisque le IRepositoryFactory serait défini dans la racine de Composition aussi bien (puisque l'application n'a pas d'affaires en utilisant une telle usine).

cela dit, dans le cas rare qu'une usine abstraite est la bonne abstraction (qui se produira typiquement lorsque vous construisez un cadre réutilisable), Je ne trouve l'utilisation d'interfaces d'usine beaucoup plus intention révélateur que l'utilisation de délégués. Il est un peu plus verbeux, mais beaucoup plus clair le sens d'une telle chose. Un IControllerFactory est plus révélateur d'intention que Func<IController> .

je dirais que cela vaut encore plus pour les usines que ne produisent pas de dépendances mais des valeurs de données à la place. Prenons par exemple l'exemple de l'injection d'un Func<DateTime> dans un constructeur. Qu'est-ce que cela signifie réellement et quelle valeur revient-il? Est-il intuitif de retourner un DateTime.Now , ou DateTime.Today , ou quelque chose d'autre? Dans ce cas, il serait beaucoup plus clair de définir une interface ITimeProvider avec une méthode GetCurrentTime() .

NOTE: Cette réponse a été mise à jour en juillet 2017 pour refléter mes dernières vues.

10
répondu Steven 2017-07-27 10:05:49

je crois qu'appeler "usine" un délégué ne devrait pas être correct.

Factory a la responsabilité d'instancier un type pris en charge, comme une implémentation de dépôt.

faire avec les délégués n'a pas de sens parce que vous définissez comment votre dépôt est instancié chaque fois que vous voulez créer une instance de votre service.

la question pour moi est pourquoi vous devriez utiliser un délégué si vous pouvez mettre en œuvre un paramètre générique Trépositaire ayant des contraintes" nouvelles "et" de classe", et, en temps de construction, instancier le dépôt à l'intérieur de votre service?

c'est juste une opinion, mais il semble que vous voulez un raccourci au lieu d'une solution meilleure, bien conçue et optimale.

Résumant:

  • usine sur un délégué permet inversion de contrôle même pour l'usine elle-même.

  • délégué sur l'usine a zéro avantage puisque vous pouvez même éliminer le besoin d'un délégué lui - même- peut-être, alors, il est inutile ou redondant -.

  • Délégué "usine" ne sera pas une usine parce que N façons de créer une instance de respository sera mis en œuvre, la rupture de la nécessité et l'avantage de l'usine.

  • il n'est pas nécessaire de créer manuellement des instances de un dépôt dans le code du consommateur. Il suffit d'utiliser un paramètre générique pour que le type de dépôt puisse être fourni afin de créer une instance appropriée, grâce à l'inversion du contrôle.

3
répondu Matías Fidemraizer 2011-03-23 11:10:46

j'aime utiliser le délégué solution car elle est plus concise. Pour éviter le problème de lisibilité des conteneurs IoC que M. Happy a mentionné, n'utilisez pas Func. Au lieu de cela créez votre propre délégué nommé.

delegate IRepository RepositoryFactory(ContextInformation context);

Maintenant vous avez le meilleur des deux mondes: la concision des délégués et la lisibilité pour les conteneurs CIO.

3
répondu enl8enmentnow 2012-08-13 22:08:53

le but de l'utilisation de l'usine abstraite au-dessus d'une usine simple est de grouper les usines individuelles.

Dans votre cas, si jamais vous avez besoin de votre délégué pour produire autre chose qu'un IRepository vous serait en difficulté..

1
répondu Richard Friend 2011-03-23 11:09:19