在存储库模式的抽象类上使用接口的优势?
可能重复:
接口与基类
通常可以看到使用Interfaces实现的存储库模式
public interface IFooRepository { Foo GetFoo(int ID); } public class SQLFooRepository : IFooRepository { // Call DB and get a foo public Foo GetFoo(int ID) {} } public class TestFooRepository : IFooRepository { // Get foo from in-memory store for testing public Foo GetFoo(int ID) {} }
但你可以使用抽象类同样做到这一点。
public abstract class FooRepositoryBase { public abstract Foo GetFoo(int ID); } public class SQLFooRepository : FooRepositoryBase { // Call DB and get a foo public override Foo GetFoo(int ID); {} } public class TestFooRepository : FooRepositoryBase { // Get foo from in-memory store for testing public override Foo GetFoo(int ID); {} }
在存储库方案中使用Interface over Abstract Class有哪些具体优势?
(即不要只是告诉我你可以实现多个接口,我已经知道了 – 你为什么要在存储库实现中这样做)
编辑澄清 – 像“ MSDN – 在类和接口之间选择 ”这样的页面可以解释为“在接口上选择类,除非有充分的理由不” – 在存储库模式的特定情况下有什么好的理由
在这个实例中,使用接口而不是抽象类的主要优点是接口是完全透明的:这是一个无法访问您inheritance的类源的问题。
但是,这种透明性允许您生成已知范围的unit testing:如果您测试一个接受接口作为参数的类(使用dependency injection方法),您就知道您正在使用已知数量测试该类; 接口的测试实现只包含您的测试代码。
同样,在测试存储库时,您知道您正在测试存储库中的代码。 这有助于限制测试中可能的变量/交互的数量。
就个人而言,我倾向于拥有一个接口,该接口保存纯粹与业务相关的方法的签名,例如Foo GetFoo()
, void DeleteFood(Foo foo)
等。我还有一个包含受保护方法的通用抽象类比如T Get()
或void Delete(T obj)
。
我将我的方法保护在抽象的Repository
类中,以便外部世界不知道plumbery( Repository
看起来像object
),而只知道通过接口的业务模型。
除了让plumbery共享另一个优点之外,我有一个可用于任何存储库的Delete
方法( protected
),但它不是公共的,所以我没有被迫在没有业务意义删除某些东西的存储库上实现它来自我的数据源。
public abstract class Repository { private IObjectSet objectSet; protected void Add(T obj) { this.objectSet.AddObject(obj); } protected void Delete(T obj) { this.objectSet.DeleteObject(obj); } protected IEnumerable (Expression> where) { return this.objectSet.Where(where); } } public interface IFooRepository { void DeleteFoo(Foo foo); IEnumerable GetItalianFoos(); } public class FooRepository : Repository , IFooRepository { public void DeleteFoo(Foo foo) { this.Delete(foo); } public IEnumerable GetItalianFoos() { return this.Find(foo => foo.Country == "Italy"); } }
在一个接口上使用抽象类的优点是,我的具体存储库不必实现它们不需要的方法(例如Delete
或Add
),但是如果需要它们就可以使用它们。 在当前的上下文中,对于某些Foos没有商业理由,因此该方法在接口上不可用。
对业务模型使用接口而不是抽象类的优点是接口提供了从业务方面操作Foo
有意义的答案(删除一些foos是否有意义?创建一些?等等) 。 unit testing时,使用此界面也更容易。 我使用的抽象Repository
不能进行unit testing,因为它通常与数据库紧密耦合。 它只能在集成测试中进行测试。 为我的存储库的业务目的使用抽象类将阻止我在unit testing中使用它们。
这是一个适用于任何类层次结构的一般问题,而不仅仅是存储库。 从纯OO的角度来看,接口和纯抽象类是相同的。
如果您的类是公共API的一部分,那么使用抽象类的主要优点是您可以在将来添加方法,而几乎不会破坏现有实现。
有些人还喜欢将接口定义为“类可以做的事情”,将基类定义为“类是什么”,因此只使用接口来实现外围function,并始终将主要function(例如存储库)定义为一类。 我不确定我的立场。
要回答你的问题,我认为在定义类的主要function时使用接口没有任何好处。
虽然其他人可能需要添加更多内容,但从纯粹实用的角度来看,大多数IoC框架在接口 – >类映射方面效果更好。 您可以在接口和类上拥有不同的可见性,而对于inheritance,可见性必须匹配。
如果您没有使用IoC框架,从我的角度来看,没有区别。 提供程序基于抽象基类。
我想关键的区别在于,抽象类可以包含私有属性和方法,其中接口不能,因为它只是一个简单的契约。
结果是界面总是“没有恶作剧 – 你看到的就是你得到的”,而抽象的基类可能会产生副作用。
由于模式源自域驱动设计,这里有一个DDD答案:
存储库的合同通常在域层中定义。 这允许域和应用程序层中的对象操纵存储库的抽象,而不关心它们的实际实现和底层存储细节 – 换句话说,是持久性无知。 此外,我们经常希望特定行为包含在某些存储库的合同中(除了你的vanilla Add(),GetById()等)所以我更喜欢ISomeEntityRepository
表单,而不仅仅是IRepository
– 我们会看到为什么他们以后需要成为接口。
另一方面,存储库的具体实现位于Infrastructure层(或测试存储库的Tests模块中)。 它们实现上述存储库合同,但也有自己的持久性特定范围。 例如,如果您正在使用NHibernate来持久保存您的实体,那么使用NHibernate会话和其他与NHibernate相关的通用管道连接到所有NHibernate存储库的超类可以派上用场。
由于您无法inheritance多个类,因此最终具体Repositoryinheritance的这两件事之一必须是一个接口。
Domain层契约作为一个接口( ISomeEntityRepository
)更合乎逻辑,因为它是一个纯粹的声明性抽象,不能对使用什么底层持久性机制做任何假设 – 即它不能实现任何东西。
特定于持久性的类可以是抽象类(基础结构层中的NHibernateRepository
或NHibernateRepository
),它允许您集中存在一些行为,这些行为对于将存在的整个持久性存储库特定的存储库是通用的。
这导致类似于:
public class SomeEntityRepository : NHibernateRepository, ISomeEntityRepository { //... }
看看Tim McCarthy的Repository Framework的实现。 < http://ddddscds.codeplex.com/ >
他使用IRepository
类的接口来定义合同,但他也使用抽象类,如RepositoryBase
或他的SqlCeRepositoryBase < T >
来实现IRepository
。 抽象基类是消除很多dublicate代码的代码。 特定于类型的存储库只需要inheritance抽象基类,并且需要为其目的添加代码。 API的用户可以通过合同对接口进行编码。
因此,您可以结合使用这两种方法来发挥它们的优势。
另外,我认为大多数IoC-Frameworks都可以处理抽象类。