在存储库模式的抽象类上使用接口的优势?

时间:2012-07-04 10:01:28

标签: c# design-patterns architecture

  

可能重复:
  Interface vs Base class

通常可以看到使用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 - Choosing Between Classes and Interfaces”这样的网页可以被解释为“除非有充分的理由不在接口上选择类” - 这是什么原因< strong>存储库模式的特定情况

7 个答案:

答案 0 :(得分:4)

就个人而言,我倾向于拥有一个接口,该接口保存纯粹与“业务相关”的方法的签名,例如Foo GetFoo()void DeleteFood(Foo foo)等。我还有一个通用的抽象类,拥有T Get()void Delete(T obj)等受保护的方法。

我在抽象Repository类中保护我的方法,以便外部世界不知道管道(Repository看起来像object),但只知道商业模型界面。

除了让plumbery共享另一个优点之外,我有一个Delete方法(protected)可用于任何存储库,但它不是公共的所以我没有被强制实现它一个存储库,它没有商业意义从我的数据源中删除一些东西。

public abstract class Repository<T>
{
    private IObjectSet objectSet;

    protected void Add(T obj)
    {
        this.objectSet.AddObject(obj);
    }

    protected void Delete(T obj)
    {
        this.objectSet.DeleteObject(obj);
    }

    protected IEnumerable<T>(Expression<Func<T, bool>> where)
    {
        return this.objectSet.Where(where);
    }
}

public interface IFooRepository
{
    void DeleteFoo(Foo foo);
    IEnumerable<Foo> GetItalianFoos();
}

public class FooRepository : Repository<Foo>, IFooRepository
{
    public void DeleteFoo(Foo foo)
    {
        this.Delete(foo);
    }

    public IEnumerable<Foo> GetItalianFoos()
    {
        return this.Find(foo => foo.Country == "Italy");
    }
}

在plumbery的接口上使用抽象类的优点是我的具体存储库不必实现他们不需要的方法(例如DeleteAdd)但它们是如果他们需要,他们可以随意使用。在目前的情况下,某些Foos没有商业理由,因此该方法在界面上不可用。

在业务模型的抽象类上使用接口的优点是接口提供了从业务方面操作Foo的有效方法的答案(删除一些foos是否有意义?创造一些?等。)单元测试时,使用此界面也更容易。我使用的抽象Repository无法进行单元测试,因为它通常与数据库紧密耦合。它只能在集成测试中进行测试。使用抽象类来实现我的存储库的业务目的会阻止我在单元测试中使用它们。

答案 1 :(得分:4)

在这个实例中,使用接口而不是抽象类的主要优点是接口是完全透明的:这是一个更难以访问您所在类的源的问题。重新继承。

但是,这种透明性允许您生成已知范围的单元测试:如果您测试一个接受接口作为参数的类(使用依赖注入方法),您就知道您正在使用已知数量;接口的测试实现只包含您的测试代码。

同样,在测试存储库时,您知道您只是在存储库中测试您的代码。这有助于限制测试中可能的变量/交互的数量。

答案 2 :(得分:3)

这是一个适用于任何类层次结构的常规问题,而不仅仅是存储库。从纯OO的角度来看,接口和纯抽象类是相同的。

如果您的类是公共API的一部分,那么使用抽象类的主要优点是您可以在将来添加方法,而几乎不会破坏现有实现。

有些人还喜欢将接口定义为“类可以做的事情”,将基类定义为“类是什么”,因此只使用接口来实现外围功能并始终定义主要功能(例如。存储库)作为一个类。我不确定我的立场。

为了回答你的问题,我认为在定义类的主要功能时使用接口没有任何好处。

答案 3 :(得分:2)

虽然其他人可能还有更多需要补充的内容,但从纯粹的实际角度来看,大多数IoC框架在界面上的效果更好 - &gt;类映射。您可以在界面上获得不同的可见性。阶级,而继承,可见性必须匹配。

如果您没有使用IoC框架,从我的角度来看,没有区别。提供程序基于抽象基类。

答案 4 :(得分:2)

我想关键的区别在于,抽象类可以包含私有属性&amp;方法,其中一个接口不能,因为它只是一个简单的合同。

结果是界面总是“没有恶作剧 - 你看到的就是你得到的”,而抽象基类可能会产生副作用。

答案 5 :(得分:2)

由于模式源自域驱动设计,这里有一个DDD答案:

存储库的合同通常在域层中定义。这允许域和应用程序层中的对象操纵存储库的抽象,而不关心它们的实际实现和底层存储细节 - 换句话说,是持久性无知。此外,我们经常希望特定行为包含在某些存储库的合同中(除了您的vanilla Add(),GetById()等),所以我更喜欢ISomeEntityRepository形式,而不仅仅是IRepository<SomeEntity> - 我们将会看到为什么他们以后需要成为接口。

另一方面,存储库的具体实现位于Infrastructure层(或测试存储库的Tests模块中)。它们实现了上述存储库契约,但也有自己的特定于持久性的特性。例如,如果你使用NHibernate来持久保存你的实体,那么使用NHibernate会话和其他与NHibernate相关的通用管道系统的所有NHibernate存储库都有一个超类可以派上用场。

由于您无法继承多个类,因此最终具体Repository继承的这两件事之一必须是一个接口。

Domain层契约作为一个接口(ISomeEntityRepository)更合乎逻辑,因为它是一个纯粹的声明性抽象,不能对使用什么底层持久性机制做任何假设 - 即它 mustn什么都不实现

特定于持久性的类可以是基础结构层中的抽象类(NHibernateRepositoryNHibernateRepository<T>),它允许您集中一些持久存储的整个范围内共有的行为 - 将存在的特定存储库。

这导致类似:

public class SomeEntityRepository : NHibernateRepository<SomeEntity>, ISomeEntityRepository 
{
  //...
}

答案 6 :(得分:1)

看一下Tim McCarthy的Repository Framework的实现。 &LT; http://dddpds.codeplex.com/&gt;

他使用IRepository<T>之类的接口来定义合同,但他也使用抽象类,如RepositoryBase<T>或他的SqlCeRepositoryBase < T >来实现IRepository<T>。抽象基类是消除大量代号的代码。特定于类型的存储库只需要继承抽象基类,并且需要为其目的添加代码。 API的用户可以通过合同对接口进行编码。

因此,您可以结合使用这两种方法来发挥它们的优势。

此外,我认为大多数IoC-Frameworks都可以处理抽象类。