J'utilise Dependency Injection (DI) depuis un moment, en injectant un constructeur, une propriété ou une méthode. Je n'ai jamais ressenti le besoin d'utiliser un conteneur Inversion of Control (IoC). Cependant, plus je lis, plus je ressens de pression de la part de la communauté pour utiliser un conteneur IoC.
J'ai joué avec des conteneurs .NET comme StructureMap , NInject , nité , et Funq . Je n'arrive toujours pas à voir comment un conteneur IoC va bénéficier/améliorer mon code.
J'ai également peur de commencer à utiliser un conteneur au travail car beaucoup de mes collègues verront du code qu'ils ne comprendront pas. Beaucoup d'entre eux peuvent être réticents à apprendre de nouvelles technologies.
S'il vous plaît, me convaincre que je dois utiliser un conteneur IoC. Je vais utiliser ces arguments lorsque je parle à mes collègues développeurs au travail.
Wow, je ne peux pas croire que Joel serait favorable à ceci:
var svc = new ShippingService(new ProductLocator(),
new PricingService(), new InventoryService(),
new TrackingRepository(new ConfigProvider()),
new Logger(new EmailLogger(new ConfigProvider())));
sur ceci:
var svc = IoC.Resolve<IShippingService>();
Beaucoup de gens ne réalisent pas que votre chaîne de dépendances peut devenir imbriquée, et il devient rapidement difficile de les câbler manuellement. Même avec des usines, la duplication de votre code n'en vaut tout simplement pas la peine.
Les conteneurs IoC peuvent être complexes, oui. Mais pour ce cas simple, j'ai montré que c'était incroyablement facile.
Ok, justifions cela encore plus. Supposons que vous souhaitiez lier certaines entités ou objets de modèle à une interface utilisateur intelligente. Cette interface utilisateur intelligente (nous l'appellerons Shindows Morms) vous demande d'implémenter INotifyPropertyChanged afin qu'elle puisse effectuer le suivi des modifications et mettre à jour l'interface utilisateur en conséquence.
"OK, ça n'a pas l'air si difficile" alors vous commencez à écrire.
Vous commencez avec ceci:
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime CustomerSince { get; set; }
public string Status { get; set; }
}
..et finissent avec this:
public class UglyCustomer : INotifyPropertyChanged
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
string oldValue = _firstName;
_firstName = value;
if(oldValue != value)
OnPropertyChanged("FirstName");
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
string oldValue = _lastName;
_lastName = value;
if(oldValue != value)
OnPropertyChanged("LastName");
}
}
private DateTime _customerSince;
public DateTime CustomerSince
{
get { return _customerSince; }
set
{
DateTime oldValue = _customerSince;
_customerSince = value;
if(oldValue != value)
OnPropertyChanged("CustomerSince");
}
}
private string _status;
public string Status
{
get { return _status; }
set
{
string oldValue = _status;
_status = value;
if(oldValue != value)
OnPropertyChanged("Status");
}
}
protected virtual void OnPropertyChanged(string property)
{
var propertyChanged = PropertyChanged;
if(propertyChanged != null)
propertyChanged(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
C'est un code de plomberie dégoûtant, et je maintiens que si vous écrivez un tel code à la main vous volez votre client. Il existe une meilleure façon de travailler, plus intelligente.
Avez-vous déjà entendu ce terme, travailler plus intelligemment, pas plus fort?
Eh bien, imaginez qu’un type intelligent de votre équipe soit venu dire: "Voici un moyen plus simple"
Si vous rendez vos propriétés virtuelles (calmez, ce n'est pas si grave), nous pouvons tisser automatiquement dans le comportement de cette propriété. (Ceci s'appelle AOP, mais ne vous inquiétez pas du nom, concentrez-vous sur ce qu'il va faire pour vous)
Selon l'outil IoC que vous utilisez, vous pouvez effectuer quelque chose qui ressemble à ceci:
var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());
Poof! Tout ce manuel BS INotifyPropertyChanged est maintenant généré automatiquement pour vous, sur chaque configurateur de propriétés virtuelles de l'objet en question.
Est-ce magique? OUI! Si vous pouvez vous fier au fait que ce code fonctionne, vous pouvez en toute sécurité ignorer toute cette propriété qui enveloppe Mumbo-Jumbo. Vous avez des problèmes commerciaux à résoudre.
Quelques autres utilisations intéressantes d'un outil IoC pour faire de l'AOP:
Je suis avec toi, Vadim. Les conteneurs IoC reposent sur un concept simple, élégant et utile et en font un outil que vous devez étudier pendant deux jours avec un manuel de 200 pages.
Personnellement, je suis perplexe devant la façon dont la communauté IoC a pris un magnifique élégant article de Martin Fowler et l'a transformé en un tas de cadres complexes, généralement dotés de manuels de 200 à 300 pages.
J'essaie de ne pas porter de jugement (HAHA!), Mais je pense que les personnes qui utilisent des conteneurs IoC sont (A) très intelligentes et (B) manquent d'empathie pour les personnes qui ne sont pas aussi intelligentes qu'elles le sont. Tout leur semble parfaitement logique, alors ils ont du mal à comprendre que de nombreux programmeurs ordinaires trouveront les concepts déroutants. C'est la malédiction de la connaissance . Les personnes qui comprennent les conteneurs IoC ont du mal à croire que certaines personnes ne le comprennent pas.
L'avantage le plus précieux de l'utilisation d'un conteneur IoC réside dans le fait que vous pouvez avoir un commutateur de configuration à un endroit qui vous permet de basculer entre, par exemple, le mode test et le mode production. Par exemple, supposons que vous ayez deux versions de vos classes d'accès à la base de données: une version qui s'est connectée de manière agressive et a fait beaucoup de validation, que vous avez utilisée pendant le développement, et une autre version sans journalisation ou validation qui était extrêmement rapide pour la production. Il est agréable de pouvoir basculer entre eux au même endroit. D’autre part, il s’agit d’un problème assez trivial, facile à gérer de manière plus simple, sans la complexité des conteneurs IoC.
Je crois que si vous utilisez des conteneurs IoC, votre code devient, franchement, beaucoup plus difficile à lire. Le nombre d'endroits que vous devez examiner pour savoir ce que le code tente de faire augmente d'au moins un. Et quelque part au ciel, un ange crie.
Vraisemblablement, personne ne vous oblige à utiliser un framework de conteneur DI. Vous utilisez déjà DI pour découpler vos cours et améliorer la testabilité. Vous bénéficiez ainsi de nombreux avantages. En bref, vous privilégiez la simplicité, ce qui est généralement une bonne chose.
Si votre système atteint un niveau de complexité où une DI manuelle devient une corvée (c'est-à-dire une maintenance accrue), comparez cela à la courbe d'apprentissage en équipe d'un cadre de conteneur de DI.
Si vous avez besoin de plus de contrôle sur la gestion de la durée de vie des dépendances (c'est-à-dire, si vous ressentez le besoin d'implémenter le modèle Singleton), examinez les conteneurs DI.
Si vous utilisez un conteneur DI, utilisez uniquement les fonctionnalités dont vous avez besoin. Ignorez le fichier de configuration XML et configurez-le à l'aide de code si cela est suffisant. S'en tenir à l'injection du constructeur. Les bases de Unity ou de StructureMap peuvent être réduites à quelques pages.
Mark Seemann a écrit un excellent article de blog sur ceci: Quand utiliser un conteneur DI
Les conteneurs IoC sont également utiles pour charger des dépendances de classe profondément imbriquées. Par exemple, si vous aviez le code suivant en utilisant Depedency Injection.
public void GetPresenter()
{
var presenter = new CustomerPresenter(new CustomerService(new CustomerRepository(new DB())));
}
class CustomerPresenter
{
private readonly ICustomerService service;
public CustomerPresenter(ICustomerService service)
{
this.service = service;
}
}
class CustomerService
{
private readonly IRespository<Customer> repository;
public CustomerService(IRespository<Customer> repository)
{
this.repository = repository;
}
}
class CustomerRepository : IRespository<Customer>
{
private readonly DB db;
public CustomerRepository(DB db)
{
this.db = db;
}
}
class DB { }
Si toutes ces dépendances sont chargées dans un conteneur IoC, vous pouvez résoudre le service client et toutes les dépendances enfants seront automatiquement résolues.
Par exemple:
public static IoC
{
private IUnityContainer _container;
static IoC()
{
InitializeIoC();
}
static void InitializeIoC()
{
_container = new UnityContainer();
_container.RegisterType<ICustomerService, CustomerService>();
_container.RegisterType<IRepository<Customer>, CustomerRepository>();
}
static T Resolve<T>()
{
return _container.Resolve<T>();
}
}
public void GetPresenter()
{
var presenter = IoC.Resolve<CustomerPresenter>();
// presenter is loaded and all of its nested child dependencies
// are automatically injected
// -
// Also, note that only the Interfaces need to be registered
// the concrete types like DB and CustomerPresenter will automatically
// resolve.
}
À mon avis, le principal avantage d'un IoC est la possibilité de centraliser la configuration de vos dépendances.
Si vous utilisez actuellement une injection de dépendance, votre code pourrait ressembler à ceci:
public class CustomerPresenter
{
public CustomerPresenter() : this(new CustomerView(), new CustomerService())
{}
public CustomerPresenter(ICustomerView view, ICustomerService service)
{
// init view/service fields
}
// readonly view/service fields
}
Si vous utilisiez une classe statique IoC, par opposition aux fichiers de configuration les plus déroutants, à mon humble avis, vous pourriez avoir quelque chose comme ceci:
public class CustomerPresenter
{
public CustomerPresenter() : this(IoC.Resolve<ICustomerView>(), IoC.Resolve<ICustomerService>())
{}
public CustomerPresenter(ICustomerView view, ICustomerService service)
{
// init view/service fields
}
// readonly view/service fields
}
Ensuite, votre classe IoC statique ressemblerait à ceci: j'utilise Unity ici.
public static IoC
{
private static readonly IUnityContainer _container;
static IoC()
{
InitializeIoC();
}
static void InitializeIoC()
{
_container = new UnityContainer();
_container.RegisterType<ICustomerView, CustomerView>();
_container.RegisterType<ICustomerService, CustomerService>();
// all other RegisterTypes and RegisterInstances can go here in one file.
// one place to change dependencies is good.
}
}
Je suis un partisan de la programmation déclarative (regardez le nombre de questions SQL auxquelles je réponds), mais les conteneurs IoC que j'ai consultés semblent trop obscurs pour leur propre bien.
... ou peut-être que les développeurs de conteneurs IoC sont incapables de rédiger une documentation claire.
... ou bien les deux sont vrais à un degré ou à un autre.
Je ne pense pas que le concept d'un conteneur IoC soit mauvais. Mais la mise en œuvre doit être suffisamment puissante (c’est-à-dire flexible) pour être utile dans une grande variété d’applications, tout en étant simple et facile à comprendre.
Il est probablement six d'une heure et demie de l'autre. Une application réelle (et non un jouet ou une démo) est forcément complexe et représente de nombreux cas délicats et exceptions à la règle. Soit vous encapsulez cette complexité dans du code impératif, soit dans du code déclaratif. Mais vous devez le représenter quelque part.
Utiliser un conteneur consiste principalement à passer d’un style d’initialisation et de configuration impératif/scripté à un style déclaratif un. Cela peut avoir quelques effets bénéfiques différents:
Bien sûr, il peut y avoir des difficultés:
Il me semble que vous avez déjà construit votre propre conteneur IoC (en utilisant les divers modèles décrits par Martin Fowler) et vous demandez pourquoi la mise en œuvre de quelqu'un d'autre est meilleure que la vôtre.
Donc, vous avez un tas de code qui fonctionne déjà. Et vous vous demandez pourquoi vous voudriez le remplacer par la mise en œuvre de quelqu'un d'autre.
Pros pour considérer un conteneur IoC tiers
inconvénients
Alors, pesez vos avantages contre vos inconvénients et prenez une décision.
Je pense que la plus grande partie de la valeur d'un IoC provient de l'utilisation de DI. Comme vous le faites déjà, le reste de l’avantage est supplémentaire.
La valeur obtenue dépend du type d'application sur lequel vous travaillez:
Pour les locataires multiples, le conteneur IoC peut prendre en charge une partie du code d'infrastructure pour le chargement de différentes ressources client. Lorsque vous avez besoin d'un composant spécifique au client, utilisez un sélecteur personnalisé pour gérer la logique et ne vous en préoccupez pas à partir de votre code client. Vous pouvez certainement le construire vous-même mais voici un exemple de la façon dont un IoC peut vous aider.
Avec de nombreux points d'extensibilité, l'IoC peut être utilisé pour charger des composants à partir de la configuration. C'est une chose courante à construire mais les outils sont fournis par le conteneur.
Si vous souhaitez utiliser AOP pour certains problèmes transversaux, le IoC fournit des points d'ancrage pour intercepter les appels de méthode. Ceci est moins communément ad-hoc sur des projets mais l'IoC facilite les choses.
J'ai déjà écrit des fonctionnalités comme celle-ci auparavant, mais si j'ai besoin de l'une de ces fonctionnalités maintenant, je préférerais utiliser un outil pré-construit et testé s'il correspond à mon architecture.
Comme mentionné par d’autres, vous pouvez également configurer de manière centralisée les classes que vous souhaitez utiliser. Bien que cela puisse être une bonne chose, cela a un coût de mauvaise orientation et de complications. Les composants de base pour la plupart des applications ne sont pas remplacés, le compromis est donc un peu plus difficile à réaliser.
J'utilise un conteneur IoC et j'apprécie la fonctionnalité, mais je dois avouer que j'ai remarqué le compromis: mon code devient plus clair au niveau de la classe et moins clair au niveau de l'application (c'est-à-dire la visualisation du flux de contrôle).
Je suis un toxicomane IOC en convalescence. J'ai du mal à justifier l'utilisation de IOC pour DI dans la plupart des cas ces jours-ci. Les conteneurs IOC sacrifient la vérification de la compilation et supposent en retour une configuration "facile", une gestion complexe de la durée de vie et la découverte à la volée de dépendances au moment de l'exécution. Je trouve que la perte de vérification du temps de compilation et la magie/les exceptions qui en résultent ne valent pas la peine, dans la grande majorité des cas. Dans les applications d'entreprise de grande taille, il peut être très difficile de suivre ce qui se passe.
Je n’achète pas l’argument de la centralisation, car vous pouvez également centraliser très facilement l’installation statique en utilisant une fabrique abstraite pour votre application et en différant religieusement la création d’objets par une fabrique abstraite, c’est-à-dire faire une DI correcte.
Pourquoi ne pas faire une DI statique sans magie comme ceci:
interface IServiceA { }
interface IServiceB { }
class ServiceA : IServiceA { }
class ServiceB : IServiceB { }
class StubServiceA : IServiceA { }
class StubServiceB : IServiceB { }
interface IRoot { IMiddle Middle { get; set; } }
interface IMiddle { ILeaf Leaf { get; set; } }
interface ILeaf { }
class Root : IRoot
{
public IMiddle Middle { get; set; }
public Root(IMiddle middle)
{
Middle = middle;
}
}
class Middle : IMiddle
{
public ILeaf Leaf { get; set; }
public Middle(ILeaf leaf)
{
Leaf = leaf;
}
}
class Leaf : ILeaf
{
IServiceA ServiceA { get; set; }
IServiceB ServiceB { get; set; }
public Leaf(IServiceA serviceA, IServiceB serviceB)
{
ServiceA = serviceA;
ServiceB = serviceB;
}
}
interface IApplicationFactory
{
IRoot CreateRoot();
}
abstract class ApplicationAbstractFactory : IApplicationFactory
{
protected abstract IServiceA ServiceA { get; }
protected abstract IServiceB ServiceB { get; }
protected IMiddle CreateMiddle()
{
return new Middle(CreateLeaf());
}
protected ILeaf CreateLeaf()
{
return new Leaf(ServiceA,ServiceB);
}
public IRoot CreateRoot()
{
return new Root(CreateMiddle());
}
}
class ProductionApplication : ApplicationAbstractFactory
{
protected override IServiceA ServiceA
{
get { return new ServiceA(); }
}
protected override IServiceB ServiceB
{
get { return new ServiceB(); }
}
}
class FunctionalTestsApplication : ApplicationAbstractFactory
{
protected override IServiceA ServiceA
{
get { return new StubServiceA(); }
}
protected override IServiceB ServiceB
{
get { return new StubServiceB(); }
}
}
namespace ConsoleApplication5
{
class Program
{
static void Main(string[] args)
{
var factory = new ProductionApplication();
var root = factory.CreateRoot();
}
}
//[TestFixture]
class FunctionalTests
{
//[Test]
public void Test()
{
var factory = new FunctionalTestsApplication();
var root = factory.CreateRoot();
}
}
}
Votre configuration de conteneur correspond à votre implémentation d'usine abstraite, vos enregistrements sont des implémentations de membres abstraits. Si vous avez besoin d'une nouvelle dépendance singleton, ajoutez simplement une autre propriété abstraite à la fabrique abstraite. Si vous avez besoin d'une dépendance transitoire, ajoutez simplement une autre méthode et injectez-la en tant que Func <>.
Avantages:
Je recommande aux sceptiques d'essayer le prochain projet de champ vert et de vous demander honnêtement à quel moment vous avez besoin du conteneur. Il est facile de prendre en compte un conteneur IOC ultérieurement, car vous venez de remplacer une implémentation d'usine par un module de configuration de conteneur IOC.
Les frameworks IoC sont excellents si vous voulez ...
... jeter la sécurité de type. De nombreux frameworks IoC (tous?) Vous obligent à exécuter le code si vous voulez être certain que tout est connecté correctement. "Hé! J'espère que j'ai tout configuré pour que mes initialisations de ces 100 classes n'échouent pas en production, en lançant des exceptions null-pointeur!"
... recouvrez votre code de globales (les frameworks IoC sont tous sur le changement d'états globaux).
... écrivez un code de merde avec des dépendances peu claires qu'il est difficile de modifier car vous ne saurez jamais ce qui dépend de quoi.
Le problème avec IoC est que ceux qui les utilisaient écrivaient du code comme celui-ci.
public class Foo {
public Bar Apa {get;set;}
Foo() {
Apa = new Bar();
}
}
ce qui est évidemment imparfait puisque la dépendance entre Foo et Bar est câblée. Ensuite, ils ont compris qu’il serait préférable d’écrire du code comme
public class Foo {
public IBar Apa {get;set;}
Foo() {
Apa = IoC<IBar>();
}
}
qui est également imparfait, mais de façon moins évidente. Dans Haskell, le type de Foo()
serait IO Foo
mais vous ne voulez vraiment pas la partie IO
- et devrait être un signe avant-coureur que quelque chose ne va pas dans votre conception si vous l'avez. .
Pour vous en débarrasser (partie IO), bénéficiez de tous les avantages des frameworks IoC et aucun de ses inconvénients ne pourrait utiliser une usine abstraite.
La solution correcte serait quelque chose comme
data Foo = Foo { apa :: Bar }
ou peut-être
data Foo = forall b. (IBar b) => Foo { apa :: b }
et injecter (mais je n'appellerais pas cela injecter) Bar.
Aussi: voyez cette vidéo avec Erik Meijer (inventeur de LINQ) où il dit que DI est destiné aux personnes qui ne connaissent pas les mathématiques (et je suis tout à fait d'accord avec cela): http://www.youtube.com/watch? v = 8Mttjyf-8P4
Contrairement à M. Spolsky, je ne crois pas que les utilisateurs de frameworks IoC soient très intelligents. Je pense simplement qu'ils ne connaissent pas les mathématiques.
Le principal avantage de l’utilisation des conteneurs IoC pour moi (personnellement, j’utilise Ninject) a été d’éliminer la transmission de paramètres et d’autres types d’objets d’état global.
Je ne programme pas pour le Web, la mienne est une application console et, souvent, au plus profond de l'arborescence d'objets, j'ai besoin d'accéder aux paramètres ou aux métadonnées spécifiées par l'utilisateur qui sont créés sur une branche complètement séparée de l'arborescence des objets. Avec IoC, je dis simplement à Ninject de traiter les paramètres comme un singleton (car il n'y en a toujours qu'une seule instance), de demander les paramètres ou le dictionnaire dans le constructeur et hop ... ils apparaissent comme par magie quand j'en ai besoin!
Sans utiliser un conteneur IoC, je devrais transmettre les paramètres et/ou les métadonnées à 2, 3, ..., n objets avant qu'ils ne soient réellement utilisés par l'objet qui en avait besoin.
Il existe de nombreux autres avantages pour les conteneurs DI/IoC tels que décrits par d’autres personnes. Passer de l’idée de créer des objets à celle de demander des objets peut être fastidieux, mais l’utilisation de DI a été très utile pour moi et mon équipe, vous pouvez donc l’ajouter. à ton arsenal!
J'ai constaté que la mise en œuvre correcte de l'injection de dépendance tend à contraindre les programmeurs à utiliser diverses autres pratiques de programmation permettant d'améliorer la testabilité, la flexibilité, la maintenabilité et l'évolutivité du code: pratiques telles que le principe de responsabilité unique, la séparation des préoccupations et le codage contre les API. Il me semble que je suis obligé d’écrire des classes et des méthodes plus modulaires et modulables, ce qui facilite la lecture du code, car il peut être assimilé à de petits morceaux.
Mais il a également tendance à créer des arbres de dépendance plutôt volumineux, qui sont beaucoup plus faciles à gérer via un framework (surtout si vous utilisez des conventions) que manuellement. Aujourd'hui, je voulais tester quelque chose très rapidement dans LINQPad, et je me suis dit que ce serait trop gênant de créer un noyau et de le charger dans mes modules, et j'ai fini par écrire ceci à la main:
var merger = new SimpleWorkflowInstanceMerger(
new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName),
new WorkflowAnswerRowUtil(
new WorkflowFieldAnswerEntMapper(),
new ActivityFormFieldDisplayInfoEntMapper(),
new FieldEntMapper()),
new AnswerRowMergeInfoRepository());
Rétrospectivement, il aurait été plus rapide d’utiliser le framework IoC, puisque les modules définissent à peu près tout cela par convention.
Après avoir passé du temps à étudier les réponses et les commentaires sur cette question, je suis convaincu que les personnes qui s’opposent à l’utilisation d’un conteneur IoC ne pratiquent pas la véritable injection de dépendance. Les exemples que j'ai vus concernent des pratiques couramment confondues avec l'injection de dépendance. Certaines personnes se plaignent de difficultés à "lire" le code. Si cela est fait correctement, la grande majorité de votre code devrait être identique lorsque vous utilisez DI à la main comme lorsque vous utilisez un conteneur IoC. La différence devrait résider entièrement dans quelques "points de lancement" au sein de l'application.
En d'autres termes, si vous n'aimez pas les conteneurs IoC, vous ne réaliserez probablement pas l'injection de dépendance comme cela est censé être fait.
Un autre point: l'injection de dépendance ne peut vraiment pas être faite à la main si vous utilisez la réflexion n'importe où. Bien que je déteste ce que la réflexion fait sur la navigation par code, vous devez reconnaître qu’il existe certains domaines où elle ne peut vraiment pas être évitée. ASP.NET MVC, par exemple, tente d'instancier le contrôleur via une réflexion sur chaque requête. Pour faire une injection de dépendance à la main, vous devriez faire chaque contrôleur une "racine de contexte", comme ceci:
public class MyController : Controller
{
private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
public MyController()
{
_simpleMerger = new SimpleWorkflowInstanceMerger(
new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName),
new WorkflowAnswerRowUtil(
new WorkflowFieldAnswerEntMapper(),
new ActivityFormFieldDisplayInfoEntMapper(),
new FieldEntMapper()),
new AnswerRowMergeInfoRepository())
}
...
}
Maintenant, comparez cela avec l’autorisation d’un framework DI de le faire pour vous:
public MyController : Controller
{
private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
{
_simpleMerger = simpleMerger;
}
...
}
En utilisant un cadre DI, notez que:
ISimpleWorkflowInstanceMerger
, je peux vérifier qu'elle est utilisée de la façon que je prévois, sans avoir besoin d'une connexion à une base de données ou quoi que ce soit.ISimpleWorkflowInstanceMerger
. Cela me permet de diviser l'application en modules séparés et de maintenir une véritable architecture à plusieurs niveaux, ce qui rend les choses beaucoup plus flexibles.Une application Web typique aura plusieurs contrôleurs. Toute la douleur de faire DI à la main dans chaque contrôleur va réellement s’ajouter à la croissance de votre application. Si vous avez une application avec une seule racine de contexte, qui n'essaye jamais d'instancier un service par réflexion, le problème n'est pas aussi grave. Néanmoins, toute application utilisant l’Injection de dépendance deviendra extrêmement coûteuse à gérer une fois qu’elle aura atteint une certaine taille, à moins que vous n’utilisiez un cadre quelconque pour gérer le graphique de dépendance.
Chaque fois que vous utilisez le "nouveau" mot-clé, vous créez une dépendance de classe concrète et une petite sonnette d'alarme doit retentir dans votre tête. Il devient plus difficile de tester cet objet isolément. La solution consiste à programmer des interfaces et à injecter la dépendance de sorte que l'objet puisse être testé par unité avec tout ce qui implémente cette interface (par exemple, mock).
Le problème est que vous devez construire des objets quelque part. Un modèle Factory est un moyen de sortir le couplage de vos POXO (Plain Old "insérez votre langage OO ici" Objets). Si vous et vos collègues écrivez tous du code comme celui-ci, un conteneur IoC est la prochaine "amélioration incrémentielle" que vous pouvez apporter à votre base de code. Cela va déplacer tout ce code vicieux d'usine sur vos objets propres et votre logique métier. Ils vont l'obtenir et l'aimer. Heck, donnez à une entreprise un discours sur les raisons pour lesquelles vous l'aimez et suscitez l'enthousiasme de tous.
Si vos collègues ne suivent pas encore l'ID, alors je vous suggérerais de vous concentrer sur cela en premier. Passez le mot sur la façon d'écrire du code propre, facilement vérifiable. Une fois que vous y êtes entré, le code DI propre est la partie la plus difficile; il devrait être relativement simple de déplacer la logique de câblage d’objet des classes Factory vers un conteneur IoC.
Comme toutes les dépendances sont clairement visibles, cela favorise la création de composants à la fois faiblement couplés, facilement accessibles et réutilisables dans l’application.
Vous n'avez pas un conteneur IoC.
Mais si vous suivez rigoureusement un modèle DI, vous constaterez qu’il en supprimera une tonne de code redondant et ennuyeux.
C'est souvent le meilleur moment pour utiliser une bibliothèque/un framework, de toute façon - quand vous comprenez ce que ça fait et pouvez le faire sans la bibliothèque.
Il se trouve que je suis en train de retirer du code d’ID développé en interne et de le remplacer par un CIO. J'ai probablement supprimé plus de 200 lignes de code et l'ai remplacé par environ 10. Oui, je devais apprendre un peu comment utiliser le conteneur (Winsor), mais je suis un ingénieur travaillant sur les technologies Internet dans le 21ème siècle, donc je suis habitué à ça. J'ai probablement passé environ 20 minutes à regarder par-dessus. Cela valait bien mon temps.
Au fur et à mesure que vous continuez à découpler vos classes et à inverser vos dépendances, les classes restent petites et le "graphe de dépendance" continue de croître. (Ce n'est pas mauvais.) L'utilisation des fonctionnalités de base d'un conteneur IoC rend le câblage de tous ces objets trivial, mais le faire manuellement peut devenir très fastidieux. Par exemple, si je veux créer une nouvelle instance de "Foo" mais qu’il faut un "Bar". Et un "Bar" nécessite un "A", un "B" et un "C". Et chacun d'entre eux a besoin de 3 autres choses, etc. etc. (oui, je ne peux pas trouver de bons faux noms :)).
L'utilisation d'un conteneur IoC pour créer votre graphique d'objet pour vous réduit la complexité et la place dans une configuration unique. Je dis simplement "crée moi un 'Foo'" et il comprend ce qui est nécessaire pour en construire un.
Certaines personnes utilisent les conteneurs IoC pour beaucoup plus d'infrastructure, ce qui est bien pour les scénarios avancés, mais dans ce cas, je conviens que cela peut masquer et rendre le code difficile à lire et à déboguer pour les nouveaux développeurs.
Dittos à propos de l'unité. Devenez trop gros et vous entendrez les craquements dans les chevrons.
Cela ne me surprend jamais quand les gens commencent à dire que le code IoC épuré ressemble au même genre de gens qui ont déjà parlé du fait que les modèles en C++ étaient une façon élégante de remonter dans les années 90, mais qu’ils les décrient aujourd'hui . Bah!
Dans le monde .NET, l'AOP n'est pas très populaire, donc pour DI, un framework est votre seule option réelle, que vous en écriviez un vous-même ou que vous utilisiez un autre framework.
Si vous avez utilisé AOP, vous pouvez injecter lors de la compilation de votre application, ce qui est plus courant en Java.
L'ID présente de nombreux avantages, tels qu'un couplage réduit, ce qui facilite les tests unitaires, mais comment allez-vous le mettre en œuvre? Voulez-vous utiliser la réflexion pour le faire vous-même?
Ça fait presque 3 ans, hein?
50% de ceux qui ont commenté en faveur des frameworks IoC ne comprennent pas la différence entre les frameworks IoC et IoC. Je doute qu'ils sachent que vous pouvez écrire du code sans être déployé sur un serveur d'applications
Si nous prenons le framework Java Spring le plus populaire, il s’agit d’une configuration IoC déplacée de XMl dans le code et qui ressemble maintenant à ceci:
`@Configuration public class AppConfig {
public @Bean TransferService transferService() {
return new TransferServiceImpl(accountRepository());
}
public @Bean AccountRepository accountRepository() {
return new InMemoryAccountRepository();
}
} `Et nous avons besoin d'un framework pour faire cela, pourquoi exactement?
Ici c'est pourquoi. Le projet s'appelle IOC-with-Ninject. Vous pouvez le télécharger et l'exécuter avec Visual Studio. Cet exemple utilise Ninject mais TOUTES les "nouvelles" instructions se trouvent au même endroit et vous pouvez complètement changer le fonctionnement de votre application en modifiant le module de liaison à utiliser. L'exemple est configuré pour vous permettre de vous connecter à une version simulée des services ou à la version réelle. Dans les petits projets, cela n'a peut-être pas d'importance, mais dans les grands projets, c'est un gros problème.
Pour être clair, les avantages que je vois sont les suivants: 1) TOUS nouveaux déclarations en un emplacement à la racine du code. 2) Totalement re-factoriser le code avec un changement. 3) Des points supplémentaires pour le "facteur cool" car c'est ... bien: cool. : p
Honnêtement, je ne trouve pas qu'il y ait beaucoup de cas où les conteneurs IoC sont nécessaires, et la plupart du temps, ils ajoutent simplement une complexité inutile.
Si vous l'utilisez uniquement pour simplifier la construction d'un objet, je dois vous demander si vous instanciez cet objet à plus d'un endroit. Un singleton ne conviendrait-il pas à vos besoins? Êtes-vous en train de changer la configuration au moment de l'exécution? (Changement de type de source de données, etc.).
Si oui, alors vous pourriez avoir besoin d'un conteneur IoC. Si ce n'est pas le cas, vous déplacez simplement l'initialisation de telle sorte que le développeur puisse la voir facilement.
Qui a dit qu'une interface valait mieux que l'héritage? Dites que vous testez un service. Pourquoi ne pas utiliser le constructeur DI et créer des simulacres des dépendances en utilisant l'héritage? La plupart des services que j'utilise n'ont que quelques dépendances. Faire des tests unitaires de cette manière évite de conserver une tonne d'interfaces inutiles et signifie que vous n'avez pas have utiliser Resharper pour trouver rapidement la déclaration d'une méthode.
Je pense que pour la plupart des implémentations, affirmer que IoC Containers supprime le code inutile est un mythe.
Premièrement, il faut commencer par installer le conteneur. Ensuite, vous devez toujours définir chaque objet à initialiser. Donc, vous ne sauvegardez pas le code lors de l'initialisation, vous le déplacez (à moins que votre objet ne soit utilisé plus d'une fois. Est-ce mieux comme Singleton?). Ensuite, pour chaque objet initialisé de cette manière, vous devez créer et gérer une interface.
Quelqu'un a un avis là dessus?
Vous auriez besoin d'un conteneur IoC si vous aviez besoin de centraliser la configuration de vos dépendances afin qu'elles puissent être facilement échangées en masse. Cela est plus logique dans TDD, où de nombreuses dépendances sont permutées, mais où il y a peu d'interdépendance entre les dépendances. Cela se fait au prix d'obstruer dans une certaine mesure le flux de contrôle de la construction d'un objet; il est donc important de disposer d'une configuration bien organisée et raisonnablement documentée. Il est également bon d’avoir une raison de le faire, sinon, c’est simplement placage d’or sur abstraction . Je l’ai vu si mal faire que cela a été ramené à l’équivalent d’une instruction goto pour les constructeurs.
Je vais essayer de trouver pourquoi IOC pourrait ne pas être bénéfique de mon point de vue.
Comme pour tout le reste, IOC conteneur (ou comme le dirait Einstein I = OC ^ 2) est un concept que vous devez décider vous-même si vous en avez besoin ou non dans votre code. Le récent tollé autour du IOC n’est que cela, la mode. Ne tombez pas dans la mode, c'est le premier. Il existe une myriade de concepts que vous pourriez implémenter dans votre code. Tout d'abord, j'utilise l'injection de dépendance depuis que j'ai commencé à programmer et j'ai appris le terme lui-même quand il a été popularisé sous ce nom. Le contrôle de la dépendance est un sujet très ancien et il a été traité jusqu’à présent par des milliards de manières, selon ce qui était en train de se dissocier de quoi. Découpler tout de tout est un non-sens. Le problème avec le conteneur IOC est qu'il essaie d'être aussi utile que Entity Framework ou NHibernate. Tandis que l'écriture d'un mappeur relationnel-objet est simplement une nécessité dès que vous devez coupler une base de données à votre système, le conteneur IOC n'est pas toujours nécessaire. Alors, quand IOC conteneur est utile:
1: Ce n'est pas souvent que votre code contient autant de dépendances, ou que vous en êtes conscient au début de la conception. La pensée abstraite est utile lorsque la pensée abstraite est due.
2: Coupler votre code avec du code tiers est un problème grave. Je travaillais avec du code datant de plus de 10 ans, qui suivait à l’époque des concepts sophistiqués et avancés tels que ATL, COM, COM +, etc. Vous ne pouvez plus rien faire avec ce code maintenant. Ce que je dis, c'est qu'un concept avancé donne un avantage apparent, mais celui-ci est annulé à long terme avec l'avantage obsolète lui-même. Cela avait juste rendu tout cela plus cher.
3: Le développement logiciel est assez difficile. Vous pouvez l'étendre à des niveaux méconnaissables si vous permettez à un concept avancé de rentrer dans votre code. Il y a un problème avec IOC2. Bien qu'il s'agisse de découpler des dépendances, il découple également le flux logique. Imaginez que vous ayez trouvé un bogue et que vous deviez définir une pause pour examiner la situation. IOC2, comme tout autre concept avancé, rend cela plus difficile. La correction d'un bogue dans un concept est plus difficile que la résolution d'un bogue dans un code plus clair, car lorsque vous corrigez un bogue, un concept doit être respecté à nouveau. (Juste pour vous donner un exemple, C++ .NET modifie constamment la syntaxe à un point tel que vous devez réfléchir sérieusement avant de refactoriser une ancienne version de .NET.) Quel est donc le problème avec IOC? Le problème réside dans la résolution des dépendances. La logique de résolution est généralement cachée dans IOC2 lui-même, écrite peut-être d'une manière peu commune que vous devez apprendre et conserver. Votre produit tiers sera-t-il disponible dans 5 ans? Microsoft n'était pas.
"Nous savons comment" le syndrome est écrit partout sur IOC2. Ceci est similaire aux tests d'automatisation. Très bon terme et solution parfaite à première vue, il vous suffit de mettre tous vos tests à exécution toute la nuit et de voir les résultats le matin. Il est très pénible d’expliquer, société après société, ce que signifie le test automatisé. Les tests automatisés ne sont certainement pas un moyen rapide de réduire le nombre de bugs que vous pouvez introduire du jour au lendemain pour améliorer la qualité de votre produit. Mais, la mode rend cette notion ennuyeusement dominante. IOC2 souffre du même syndrome. On pense que vous devez l'implémenter pour que votre logiciel soit performant. Chaque entretien récent m'a demandé si je mettais en œuvre IOC2 et l'automatisation. C'est un signe de mode: la société avait une partie du code écrite en MFC qu'elle n'abandonnerait pas.
Vous devez apprendre IOC2 comme n'importe quel autre concept logiciel. La décision d'utiliser ou non IOC2 appartient à l'équipe et à l'entreprise. Cependant, au moins TOUS les arguments ci-dessus doivent être mentionnés avant que la décision ne soit prise. Ce n'est que si vous voyez que le côté positif l'emporte sur le côté négatif que vous pouvez prendre une décision positive.
IOC2 n'a rien d'anormal, sauf qu'il résout uniquement les problèmes qu'il résout et introduit les problèmes qu'il introduit. Rien d'autre. Cependant, aller à contre-courant est très difficile, ils ont la bouche en sueur, les adeptes de n'importe quoi. Il est étrange qu’aucun d’entre eux ne soit présent lorsque le problème de leur fantaisie devient évident. De nombreux concepts de l'industrie du logiciel ont été défendus car ils génèrent des bénéfices, des livres sont écrits, des conférences sont organisées, de nouveaux produits sont fabriqués. C'est la mode, généralement de courte durée. Dès que les gens trouvent quelque chose d'autre, ils l'abandonnent complètement. IOC2 est utile mais montre les mêmes signes que beaucoup d’autres concepts évanouis que j’ai vus. Je ne sais pas si ça va survivre. Il n'y a pas de règle pour ça. Vous pensez que si cela est utile, il survivra. Non, ça ne se passe pas comme ça. Une grande entreprise riche suffit et le concept peut disparaître en quelques semaines. On verra. NHibernate a survécu, EF est arrivé deuxième. Peut-être que IOC2 survivra aussi. N'oubliez pas que la plupart des concepts de développement logiciel ne sont rien de spécial, qu'ils sont très logiques, simples et évidents, et qu'il est parfois plus difficile de se rappeler la convention de dénomination actuelle que de comprendre le concept lui-même. Est-ce que la connaissance d'IOC2 fait d'un développeur un meilleur développeur? Non, car si un développeur n'était pas en mesure de proposer un concept de nature similaire à IOC2, il lui serait difficile de comprendre quel problème IOC2 est en train de résoudre. Son utilisation semblera artificielle et il pourra commencer à l'utiliser. pour être une sorte de politiquement correct.
Personnellement, j'utilise IoC comme une sorte de carte de structure de mon application (oui, je préfère aussi StructureMap;)). Cela facilite la substitution de mes implémentations habituelles d’interface par des implémentations de Moq lors des tests. Créer une configuration de test peut être aussi simple que de faire un nouvel appel init de mon framework IoC, en substituant une classe à la classe de ma limite de test.
Ce n’est probablement pas ce pour quoi IoC est là, mais c’est ce pour quoi je me trouve le plus souvent.
LE CONTENEUR IOC RÉSOLUE UN PROBLÈME QUE VOUS N'AVEZ PAS POSSIBLE, MAIS C’EST UN PROBLÈME JOLI À AVOIR
Je me rends compte qu’il s’agit d’un article plutôt ancien, mais il semble rester assez actif et j’ai pensé contribuer à un ou deux points qui n’ont pas encore été mentionnés dans d’autres réponses.
Je dirai que je suis d'accord avec les avantages de l'injection de dépendance, mais je préfère construire et gérer les objets moi-même, en utilisant un modèle semblable à celui décrit par Maxm007 dans sa réponse. J'ai constaté deux problèmes principaux lors de l'utilisation de conteneurs tiers:
1) Avoir une bibliothèque tierce gérer la durée de vie de vos objets "automatiquement" peut donner des résultats inattendus. Nous avons constaté qu'en particulier dans les grands projets, vous pouvez avoir beaucoup plus de copies d'un objet que prévu, et plus que si vous géiez manuellement les cycles de vie. Je suis sûr que cela varie en fonction du cadre utilisé, mais le problème existe néanmoins. Cela peut également être problématique si votre objet contient des ressources, des connexions de données, etc., car l'objet peut parfois vivre plus longtemps que prévu. Les conteneurs IoC ont donc inévitablement tendance à augmenter l'utilisation des ressources et l'encombrement de la mémoire d'une application.
2) Les conteneurs IoC, à mon avis, sont une forme de "programmation par boîte noire". J'ai constaté qu'en particulier, nos développeurs moins expérimentés ont tendance à en abuser. Cela permet au programmeur de ne pas avoir à réfléchir à la manière dont les objets doivent être liés les uns aux autres ou à la manière de les découpler, car cela leur fournit un mécanisme leur permettant de simplement saisir n'importe quel objet de leur choix. Par exemple, il peut y avoir une bonne raison de conception pour qu'ObjectA ne connaisse jamais ObjectB directement, mais plutôt que de créer une usine, un pont ou un localisateur de service, un programmeur inexpérimenté dira simplement "pas de problème, je vais simplement extraire ObjectB du conteneur IoC ". Cela peut en réalité conduire à une augmentation du couplage des objets, ce que IoC est censé aider à prévenir.
vous n'avez pas besoin d'un framework pour réaliser l'injection de dépendance. Vous pouvez le faire en utilisant les concepts de base Java. http://en.wikipedia.org/wiki/Dependency_injection#Code_illustration_using_Java