IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Architecture en couches, découplage et injection de dépendances avec Unity

Architecture en couches, découplage et injection de dépendances avec Unity


précédentsommairesuivant

III. Unity, principe et mise en oeuvre simple

III-A. Présentation

Unity peut se configurer de deux façons différentes: par code ou par fichier de configuration. Afin d'être plus flexible, le fichier de configuration est souvent utilisé. Cependant, dans des cas comme une application Silverlight où la notion de fichier de configuration n'existe pas, la déclaration par code est utilisé.
On peut voir Unity comme une usine à objet: on demande un objet d'un certain type ou implémentant une certaine interface et Unity nous retourne une implémentation obéissant à plusieurs stratégies (cela peut être un singleton, une nouvelle entité à chaque appel, une entité par thread, etc).
Ces entités sont stockées dans des containers.

III-B. Configuration

Il faut tout d'abord ajouter les références vers les dll Unity dans le projet console:

  • Microsoft.Pratices.Unity
  • Microsoft.Pratices.Unity.Configuration

Il faut ensuite créer un fichier de configuration App.Config. Unity peut également utiliser un autre fichier pour la configuration. C'est utile dans une grosse application lorsqu'il y a beaucoup d'entités à déclarer.

Fichier de configuration vide
Sélectionnez
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
</configuration>

Le premier élément à ajouter est l'import de la balise unity, cela se fait en rajoutant:

Import de la section de configuration Unity
Sélectionnez
<configSections>
	<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
</configSections>

On peut désormais utiliser la balise Unity dans notre configuration et déclarer nos entités dedans.

Astuce! Il est possible de déclarer l'utilisation du namespace unity lors de la déclaration de la balise unity. Ainsi, l'autocomplétion est disponible.

 
Sélectionnez
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
</unity>

Nous allons dans un premier temps déclarer les Assemblies et Namespaces dans lesquels Unity doit trouver nos types et nos interfaces à instancier. Pour déclarer une assembly, il suffit d'utiliser une balise assembly et pour un namespace c'est la balise namespace. Dans notre exemple, il nous faut utiliser les assemblies et namespaces suivants:

  • MovieBase.Business.Interfaces
  • MovieBase.DataAccess.Interfaces
  • MovieBase.Business
  • MovieBase.XmlDataAccess
 
Sélectionnez
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">

	<assembly name="MovieBase.Business.Interfaces" />
	<assembly name="MovieBase.Business" />
	<assembly name="MovieBase.DataAccess.Interfaces" />
	<assembly name="MovieBase.XmlDataAccess" />
		
	<namespace name="MovieBase.Business.Interfaces" />
	<namespace name="MovieBase.Business" />
	<namespace name="MovieBase.DataAccess.Interfaces" />
	<namespace name="MovieBase.XmlDataAccess" />
		
</unity>

Une fois cette étape effectuée, il faut déclarer nos entités avec éventuellement un mapping: On peut soit déclarer une interface et lui associer une classe d'implémentation (c'est un mapping), soit déclarer directement une classe. La déclaration de classe est cependant peu utilisée: elle brise le découplage.
Ces déclarations se font à l'intérieur d'une balise container. Voici ce que donne le fichier de configuration résultant:

 
Sélectionnez
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<configSections>
		<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration"/>
	</configSections>

	<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">

		<assembly name="MovieBase.Business.Interfaces" />
		<assembly name="MovieBase.Business" />
		<assembly name="MovieBase.DataAccess.Interfaces" />
		<assembly name="MovieBase.XmlDataAccess" />
		
		<namespace name="MovieBase.Business.Interfaces" />
		<namespace name="MovieBase.Business" />
		<namespace name="MovieBase.DataAccess.Interfaces" />
		<namespace name="MovieBase.XmlDataAccess" />

		<container>
			<register type="IDataAccess" mapTo="DataAccess" />
			<register type="IBusinessLogic" mapTo="BusinessLogic" />
		</container>
	</unity>
</configuration>

Il ne reste plus qu'à utiliser ceci dans le code!

III-C. Utilisation

La première chose à faire est de supprimer le membre BusinessLogic de notre classe Program. Nous allons plutôt utiliser un membre de type IUnityContainer qui sera le container de nos entités. Puis il faut le configurer et enfin l'utiliser pour obtenir une instance de la couche business.
La configuration du container se fait en créant une nouvelle instance d'un UnityContainer puis en appellant la méthode d'extension LoadConfiguration().
Pour obtenir une instance implémentant IBusinessLogic, il suffit d'appeller la méthode d'extension Resolve<T>() sur le container.
Voici le code modifié pour notre utilisation:

 
Sélectionnez
	class Program
	{
		private static IUnityContainer _container;
		
		static void Main(string[] args)
		{
			//Unity configuration
			_container = new UnityContainer().LoadConfiguration();

			while (true)
			{
				DisplayMenu();

				var choice = System.Console.ReadLine();

				if (choice == "q")
					break;

				int nChoice;
				if (!int.TryParse(choice, out nChoice))
					System.Console.WriteLine("Choix inconnu!");
				else
					TreatChoice(nChoice);

				System.Console.ReadLine();
			}
		}

		private static void TreatChoice(int i)
		{
			var businessLayer = _container.Resolve<IBusinessLogic>();

			String country;
			ushort year;
			List<Movie> movies;
			switch (i)
			{
				case 1:
					movies = businessLayer.GetMovies();
					foreach (var movie in movies)
						System.Console.WriteLine("{0}: {1} - {2} - {3}", movie.Id, movie.Title, movie.Year, movie.Country);
					break;
				case 2:
					System.Console.WriteLine("Enter year:");
					year = ushort.Parse(System.Console.ReadLine());
					movies = businessLayer.GetMoviesByYear(year);
					foreach (var movie in movies)
						System.Console.WriteLine("{0}: {1} - {2} - {3}", movie.Id, movie.Title, movie.Year, movie.Country);
					break;
				case 3:
					System.Console.WriteLine("Enter country:");
					country = System.Console.ReadLine();
					movies = businessLayer.GetMoviesByCountry(country);
					foreach (var movie in movies)
						System.Console.WriteLine("{0}: {1} - {2} - {3}", movie.Id, movie.Title, movie.Year, movie.Country);
					break;
				case 4:
					System.Console.WriteLine("Enter year:");
					year = ushort.Parse(System.Console.ReadLine());
					System.Console.WriteLine("Enter country:");
					country = System.Console.ReadLine();
					movies = businessLayer.GetMoviesByYearAndCountry(year, country);
					foreach (var movie in movies)
						System.Console.WriteLine("{0}: {1} - {2} - {3}", movie.Id, movie.Title, movie.Year, movie.Country);
					break;
			}
		}

		private static void DisplayMenu()
		{
			System.Console.Clear();
			System.Console.WriteLine("1 to display all");
			System.Console.WriteLine("2 to filter by year");
			System.Console.WriteLine("3 to filter by country");
			System.Console.WriteLine("4 to filter by year & country");

			System.Console.WriteLine("q to quit");
		}
	}

On remarque que contrairement à la première version, nous ne nous sommes pas préoccupés de l'instanciation de la couche d'accès aux données. Unity s'est aperçu que pour générer une instance de la couche business, le constructeur avait besoin d'une implémentation d'une couche d'accès aux données. On dit qu'il avait une dépendance dessus. Il a donc dans un premier temps généré une instance de la couche d'accès aux données puis il l'a passée au constructeur de la couche business. C'est l'injection de dépendances! Pour vous en convaincre, vous pouvez poser des points d'arrêts sur les différents constructeurs.

Attention aux références! En effet, on s'aperçoit qu'à aucun moment du code on utilise explicitement les types des assemblies Business et XmlDataAccess. Il serait tentant de supprimer les références projets ce qui serait totalement valable et justifié. Cependant, je préfère les garder pour bénéficier du build en cascade et de la recopie automatique dans le dossier de sortie. Si les références sont enlevées, il faut penser à copier manuellement les assemblies dans le dossier de sortie.

Désormais, notre application fonctionne, est découplée et utilise l'injection de dépendances. Nous allons pousser les expériences en découvrant le pattern de ServiceLocator, jouer sur les durées de vie de nos entités et voir les implémentations dans les différents projets possibles.


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2011 Nathanael Marchand. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.