C'est une question d'intimité. Si vous codez vers une implémentation (un objet réalisé), vous êtes dans une relation assez intime avec cet "autre" code, en tant que consommateur de celui-ci. Cela signifie que vous devez savoir comment le construire (c'est-à-dire quelles sont les dépendances qu'il a, éventuellement en tant que paramètres de constructeur, éventuellement en tant que setters), quand en disposer, et vous ne pouvez probablement pas faire grand chose sans lui.
Une interface devant l'objet réalisé vous permet de faire quelques choses -
- D'une part, vous pouvez/devriez utiliser une fabrique pour construire des instances de l'objet. Les conteneurs IOC le font très bien pour vous, ou vous pouvez créer le vôtre. Avec des tâches de construction hors de votre responsabilité, votre code peut simplement supposer qu'il obtient ce dont il a besoin. De l'autre côté du mur de la fabrique, vous pouvez construire soit des instances réelles, soit des instances factices de la classe. En production, vous utiliserez bien sûr des instances réelles, mais pour les tests, vous pouvez créer des instances simulées ou dynamiquement simulées pour tester différents états du système sans avoir à exécuter le système.
- Il n'est pas nécessaire de savoir où se trouve l'objet. Ceci est utile dans les systèmes distribués où l'objet auquel vous voulez parler peut ou non être local à votre processus ou même à votre système. Si vous avez déjà programmé Java RMI ou EJB old skool, vous connaissez la routine de "parler à l'interface" qui cachait un proxy qui a fait le réseau distant et les devoirs de marshalling que votre client n'a pas dû se soucier. WCF a une philosophie similaire de "parler à l'interface" et laisser le système déterminer comment communiquer avec l'objet/service cible.
** MISE À JOUR ** Il y a eu une demande pour un exemple d'un conteneur IOC (Factory). Il en existe de nombreux pour pratiquement toutes les plates-formes, mais ils fonctionnent essentiellement de la manière suivante :
-
Vous initialisez le conteneur dans la routine de démarrage de vos applications. Certains frameworks le font via des fichiers de configuration, du code ou les deux.
-
Vous "enregistrez" les implémentations que vous souhaitez que le conteneur crée pour vous en tant que fabrique pour les interfaces qu'elles mettent en œuvre (par exemple, enregistrer MyServiceImpl pour l'interface Service). Au cours de ce processus d'enregistrement, il existe généralement une politique comportementale que vous pouvez définir, par exemple si une nouvelle instance est créée à chaque fois ou si une seule instance est utilisée.
-
Lorsque le conteneur crée des objets pour vous, il injecte toutes les dépendances dans ces objets dans le cadre du processus de création (c'est-à-dire que si votre objet dépend d'une autre interface, une implémentation de cette interface est fournie à son tour, et ainsi de suite).
De manière pseudo-codifiée, cela pourrait ressembler à ceci :
IocContainer container = new IocContainer();
//Register my impl for the Service Interface, with a Singleton policy
container.RegisterType(Service, ServiceImpl, LifecyclePolicy.SINGLETON);
//Use the container as a factory
Service myService = container.Resolve<Service>();
//Blissfully unaware of the implementation, call the service method.
myService.DoGoodWork();