简体   繁体   中英

InversionOfControl in a new MVC3 project

I'm building an e-commerce site using C#, MVC3, Entity Framework 4, my first stab at MVC3 and Entity Framework so I want to ensure a sound architecture. In particular, I'm questioning my usage of Interfaces and Dependency Inversion as they pertain to the Services and Repository layers. I'll focus on one area of the system, the Cart system, for brevity and clarity.

Here's an Interface Inversion example PluralSite uses to explain the typical tendency to create interfaces without consideration of proper dependencies.

Lets say you have an IKangaroo interface that a "BoxingMatch" class depends on. When you add more "boxers" like IMikeTyson and IJoeBoxer, you now have three different Interfaces, one for each boxer, that "BoxingMatch" needs to know about. IKangaroo, IMikeTyson, and IJoeBoxer only have one concrete implementation each, which means you don't even need those interfaces (you may as well make BoxingMatch depend directly on the concrete Kangaroo, MikeTyson, and JoeBoxer classes). Further, it doesn't even make sense that there would be more than one implementation of IKangaroo, or IMikeTyson. So the interfaces are needless and don't bring any value to the architecture.

Inverting the dependencies in this example would result in "BoxingMatch" defining the interface that the classes its going to use (Kangaroo, MikeTyson and JoeBoxer) are going to implement. So, "BoxingMatch" would depend on an IBoxer interface, and Kangaroo, MikeTyson and JoeBoxer will all implement IBoxer. There's the inversion, and it makes perfect sense.

Now, my situation... The CartController constructor has two dependency parameters injected, ICartService and IProductService). CartService takes a single injected constructor argument (ICartRepository). CartController calls AddItem() on CartService, and CartService calls AddItem() on CartRepository.

ICartRepository has two implementations: CartRepository (in the main Web project) and TestCartRepository (in the Tests project). ICartService only has a single implementation.

My questions: how does my architecture stack up to the lesson in the above example? I don't really see how my CartController could be less-coupled than it already is to the CartService. The controller only depends on CartService, not ICartRepository. So it doesn't seem I can invert control here by having the CartController define which interface CartService and ICartRepository are going to use, since CartService and CartRepository are different layers entirely. Am I correct?

Down one level, and same question. CartService depends on CartRepository. Would the above inversion principle apply here? Or have I already inverted the dependency by requiring an ICartRepository injected parameter in the CartService constructor?

So my question really is, did I do this "right"?

Any thoughts or advice would be appreciated.

My code, for reference:

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 (implementation):

//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 (implementation):

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

You mistakenly seem to believe that the only reason to use IoC is to provide multiple implementations. In fact, this is one of the least useful reasons to use IoC.

By using an IoC container, you can manage object lifetimes (for instance, by making objects live for the life of a single web request).

An IoC container also handles recursive dependencies. If you create a IMikeTyson and IMikeTyson depends on IBoxingShorts, then you don't have to instantiate the boxing shorts, they come along for free.

All this ignores the big reason to use IoC, and that's to make unit testing easier. By using interfaces, you can supply mock IMikeTysons for your unit tests of BoxingMatch so that you can feed the BoxingMatch known values, and not have to reset your database after each test.

And, there are about 100 other benefits that IoC and Interface based programming bring to the table.

I think you're overthinking your Cart scenario. You just need to pass the instances you depend on, and don't worry too much about the academic definition of IoC.

This probably doesn't answer all your questions, but if at some point you do intend separating the process between Controller and Service layers, eg by using WCF, then your I*Services would become WCF Service Contracts - this would reinforce the decision to couple layers via interfaces and not directly coupled to the classes.

In this case, an additional facade / indirection can be added, often called a service agent, whereby the 'exact' SOA service interfaces are hidden from the controller. This would allow the Service tier to be replaced by one of similar functionality, but using different service interfaces.

Your inversion looks fine - your layers depend on an abstraction (interfaces) and hide the implementation, so *Service wouldn't be aware that the repository used Entity Framework, NHibernate or DataSets, nor would the Controller know that the Service layer used a repository, or accessed the database directly (the injection constructors won't be exposed on the interfaces consumed by the clients of the layer, so no worries here).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM