4 votes

InversionOfControl dans un nouveau projet MVC3

Je suis en train de construire un site de commerce électronique en utilisant C#, MVC3, Entity Framework 4. C'est la première fois que j'utilise MVC3 et Entity Framework et je veux m'assurer que l'architecture est solide. En particulier, je m'interroge sur l'utilisation que je fais des interfaces et de l'inversion de dépendances dans le cadre des couches Services et Référentiel. Je vais me concentrer sur une seule partie du système, le système de panier, pour des raisons de brièveté et de clarté.

Voici un exemple d'inversion d'interface utilisé par PluralSite pour expliquer la tendance typique à créer des interfaces sans tenir compte des dépendances appropriées.

Disons que vous avez une interface IKangaroo dont dépend une classe "BoxingMatch". Si vous ajoutez d'autres "boxeurs" comme IMikeTyson et IJoeBoxer, vous avez maintenant trois interfaces différentes, une pour chaque boxeur, que "BoxingMatch" doit connaître. IKangaroo, IMikeTyson et IJoeBoxer n'ont qu'une seule implémentation concrète chacune, ce qui signifie que vous n'avez même pas besoin de ces interfaces (vous pouvez tout aussi bien faire dépendre BoxingMatch directement des classes concrètes Kangaroo, MikeTyson et JoeBoxer). De plus, il n'est même pas logique qu'il y ait plus d'une implémentation de IKangaroo, ou IMikeTyson. Les interfaces sont donc inutiles et n'apportent aucune valeur à l'architecture.

En inversant les dépendances dans cet exemple, "BoxingMatch" définirait l'interface que les classes qu'il va utiliser (Kangourou, MikeTyson et JoeBoxer) vont mettre en œuvre. Ainsi, "BoxingMatch" dépendrait d'une interface IBoxer, et Kangaroo, MikeTyson et JoeBoxer implémenteraient tous IBoxer. Voilà l'inversion, et elle est parfaitement logique.

Maintenant, ma situation... Le constructeur de CartController a deux paramètres de dépendance injectés, ICartService et IProductService). CartService prend un seul argument de constructeur injecté (ICartRepository). CartController appelle AddItem() sur CartService, et CartService appelle AddItem() sur CartRepository.

ICartRepository a deux implémentations : CartRepository (dans le projet Web principal) et TestCartRepository (dans le projet Tests). ICartService n'a qu'une seule implémentation.

Mes questions : comment mon architecture se situe-t-elle par rapport à la leçon de l'exemple ci-dessus ? Je ne vois pas vraiment comment mon CartController pourrait être moins couplé qu'il ne l'est déjà au CartService. Le contrôleur ne dépend que de CartService, pas de ICartRepository. Il ne semble donc pas que je puisse inverser le contrôle ici en demandant au CartController de définir l'interface que CartService et ICartRepository vont utiliser, puisque CartService et CartRepository sont des couches entièrement différentes. Ai-je raison ?

Un niveau plus bas, et même question. CartService dépend de CartRepository. Le principe d'inversion ci-dessus s'appliquerait-il ici ? Ou ai-je déjà inversé la dépendance en exigeant un paramètre injecté ICartRepository dans le constructeur de CartService ?

Donc ma question est vraiment, est-ce que j'ai fait ça "correctement" ?

Toute réflexion ou tout conseil serait apprécié.

Mon code, pour référence :

CartController :

//constructor
public CartController(ICartService cartService, IProductService productService)
{
    _cartService = cartService;
    _productService = productService;
}

public RedirectToRouteResult AddItem(Cart cart, int productId)
{
    var product = _productService.GetProduct(productId);
    if (product != null)
    {
        _cartService.AddItem(cart, product, 1);
    }
    return RedirectToAction("Index");
}

CartService (implémentation) :

//constructor
public CartService(ICartRepository repository)
{
    _repository = repository;
}

public void AddItem(Cart cart, Product product, int quantity)
{
    //simplified for brevity
    var cartProduct = _repository.CartProducts().SingleOrDefault(cp => cp.CartId == cart.CartId && cp.ProductId == product.ProductId);
    _repository.AddCartItem(cartProduct);
}

Cart Repository (implémentation) :

public void AddCartItem(CartProduct cartProduct)
{
    _context.CartProducts.Add(cartProduct);
    _context.SaveChanges();
}

4voto

Erik Funkenbusch Points 53436

Vous semblez croire, à tort, que la seule raison d'utiliser IoC est de fournir de multiples implémentations. En fait, c'est l'une des raisons les moins utiles d'utiliser IoC.

En utilisant un conteneur IoC, vous pouvez gérer la durée de vie des objets (par exemple, en les faisant vivre le temps d'une seule requête web).

Un conteneur IoC gère également les dépendances récursives. Si vous créez un IMikeTyson et que cet IMikeTyson dépend de IBoxingShorts, vous n'avez pas besoin d'instancier les shorts de boxe, ils sont fournis gratuitement.

Tout cela ne tient pas compte de la principale raison d'utiliser IoC, à savoir faciliter les tests unitaires. En utilisant des interfaces, vous pouvez fournir des IMikeTysons fantaisie pour vos tests unitaires de BoxingMatch, de sorte que vous puissiez fournir à BoxingMatch des valeurs connues, et ne pas avoir à réinitialiser votre base de données après chaque test.

Et il y a une centaine d'autres avantages que l'IoC et la programmation basée sur l'interface apportent à la table.

Je pense que vous réfléchissez trop à votre scénario Cart. Vous avez juste besoin de passer les instances dont vous dépendez, et ne vous inquiétez pas trop de la définition académique de IoC.

1voto

StuartLC Points 35534

Cela ne répond probablement pas à toutes vos questions, mais si, à un moment donné, vous avez l'intention de séparer le processus entre les couches contrôleur et service, par exemple en utilisant WCF, alors vos I*Services deviendraient des contrats de service WCF - cela renforcerait la décision de coupler les couches via des interfaces et non directement couplées aux classes.

Dans ce cas, il est possible d'ajouter une façade ou une indirection supplémentaire, souvent appelée agent de service, qui masque les interfaces de service SOA "exactes" au contrôleur. Cela permet de remplacer le niveau de service par un niveau de fonctionnalité similaire, mais utilisant des interfaces de service différentes.

Votre inversion a l'air bien - vos couches dépendent d'une abstraction (interfaces) et cachent l'implémentation, donc *Service ne saurait pas que le référentiel utilise Entity Framework, NHibernate ou DataSets, et le contrôleur ne saurait pas non plus que la couche Service utilise un référentiel, ou accède directement à la base de données (les constructeurs d'injection ne seront pas exposés sur les interfaces consommées par les clients de la couche, donc pas de soucis ici).

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