我正在使用C#,MVC3,Entity Framework 4构建一个电子商务网站,这是我第一次尝试使用MVC3和Entity Framework,所以我想确保一个健全的架构。特别是,我正在质疑我对Interfaces和Dependency Inversion的使用,因为它们与Services和Repository层有关。为了简洁和清晰起见,我将专注于系统的一个区域,即推车系统。
这是一个Interface Inversion示例,PluralSite用于解释在不考虑适当依赖性的情况下创建接口的典型趋势。
假设您有一个“BoxingMatch”类依赖的IKangaroo界面。当你添加更多“拳击手”时,比如IMikeTyson和IJoeBoxer,你现在有三个不同的界面,每个拳击手一个,“BoxingMatch”需要知道。 IKangaroo,IMikeTyson和IJoeBoxer每个只有一个具体的实现,这意味着你甚至不需要那些接口(你也可以让BoxingMatch直接依赖于具体的袋鼠,MikeTyson和JoeBoxer类)。此外,IKangaroo或IMikeTyson的实施方案甚至没有意义。因此接口是不必要的,不会给架构带来任何价值。
在此示例中反转依赖项将导致“BoxingMatch”定义将要使用的类(Kangaroo,MikeTyson和JoeBoxer)将要实现的接口。因此,“BoxingMatch”将依赖于IBoxer接口,Kangaroo,MikeTyson和JoeBoxer都将实施IBoxer。有反转,它很有道理。
现在,我的情况...... CartController构造函数注入了两个依赖参数,ICartService和IProductService。 CartService采用单个注入的构造函数参数(ICartRepository)。 CartController在CartService上调用AddItem(),CartService在CartRepository上调用AddItem()。
ICartRepository有两个实现:CartRepository(在主Web项目中)和TestCartRepository(在Tests项目中)。 ICartService只有一个实现。
我的问题:我的架构是如何与上述示例中的课程相结合的?我真的没有看到我的CartController如何比CartService更少耦合。控制器仅依赖于CartService,而不依赖于ICartRepository。因此,我可以通过让CartController定义哪个接口CartService和ICartRepository将使用来反转控制,因为CartService和CartRepository完全是不同的层。我是对的吗?
降低一级,同样的问题。 CartService依赖于CartRepository。上述反演原则是否适用于此?或者我已经通过在CartService构造函数中要求ICartRepository注入参数来反转依赖关系吗?
所以我的问题是,“我做对了吗?”
任何想法或建议都将不胜感激。
我的代码,供参考:
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(实施):
//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);
}
购物车存储库(实施):
public void AddCartItem(CartProduct cartProduct)
{
_context.CartProducts.Add(cartProduct);
_context.SaveChanges();
}
答案 0 :(得分:4)
您似乎错误地认为使用IoC的唯一原因是提供多种实现。实际上,这是使用IoC最不实用的原因之一。
通过使用IoC容器,您可以管理对象生存期(例如,通过在单个Web请求的生命周期中使对象生效)。
IoC容器还处理递归依赖项。如果你创建一个IMikeTyson并且IMikeTyson依赖于IBoxingShorts,那么你不必实例化拳击短裤,它们是免费的。
所有这些都忽略了使用IoC的重要原因,那就是让单元测试变得更容易。通过使用接口,您可以为BoxingMatch的单元测试提供模拟IMikeTysons,以便您可以提供BoxingMatch已知值,而不必在每次测试后重置数据库。
而且,基于IoC和接口的编程还有大约100个其他好处。
我认为你正在过度思考购物车的情景。您只需要传递您所依赖的实例,而不必过分担心IoC的学术定义。
答案 1 :(得分:1)
这可能无法解答您的所有问题,但如果您在某个时候打算将Controller和Service层之间的流程分开,例如通过使用WCF,您的I *服务将成为WCF服务合同 - 这将加强通过接口耦合图层而不是直接耦合到类的决定。
在这种情况下,可以添加一个额外的Facade / indirection,通常称为服务代理,从而对控制器隐藏“确切的”SOA服务接口。这将允许服务层被类似功能之一替换,但使用不同的服务接口。
您的反转看起来很好 - 您的图层依赖于抽象(接口)并隐藏实现,因此* Service不会意识到存储库使用了Entity Framework,NHibernate或DataSets,Controller也不会知道服务层使用存储库,或直接访问数据库(注入构造函数不会暴露在图层客户端使用的接口上,因此不用担心)。