15

可能重复:
接口与基类

使用接口实现的存储库模式很常见

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); {}
}

在存储库场景中使用接口而不是抽象类的具体优势是什么?

(即不要只是告诉我你可以实现多个接口,我已经知道这一点 - 你为什么要在存储库实现中这样做)

编辑以澄清- “ MSDN - 在类和接口之间选择”之类的页面可以解释为“在接口上选择类,除非有充分的理由不这样做” - 在存储库模式的特定情况下,好的理由是什么

4

7 回答 7

4

Personally, I tend to have an interface that holds the signature for the methods that are purely "business-related" for example Foo GetFoo(), void DeleteFood(Foo foo), etc. I also have a generic abstract class that holds protected methods like T Get() or void Delete(T obj).

I keep my methods protected in the abstract Repository class so that the outside world is not aware of the plumbery (Repository will look like object) but only of the business model via the interface.

On top of having the plumbery shared another advantage is that I have for example a Delete method (protected) available to any repository but it is not public so I am not forced to implement it on a repository where it has no business meaning to delete something from my data source.

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");
    }
}

The advantage of using the abstract class over an interface for the plumbery is that my concrete repositories do not have to implement method they don't need (Delete or Add for example) but they are at their disposal if they need it. In the current context, there is no business reason for to some Foos so the method is not available on the interface.

The advantage of using an interface over an abstract class for the business model is that the interface provides the answers to how it make sense to manipulate Foo from a business side (does it make sense to Delete some foos? To create some? etc.). It's also easier to use this interface when Unit testing. The abstract Repository I use cannot be unit tested because it is usually tightly coupled with the database. It can only be tested in integration tested. Using an abstract class for the business purpose of my repositories would prevent me from using them in unit tests.

于 2012-07-04T10:09:29.493 回答
4

在这种情况下,使用接口而不是抽象类的主要优点是接口是完全透明的:这更多的是您无法访问要继承的类的源代码的问题。

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

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

于 2012-07-04T10:46:19.410 回答
3

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

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

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

要回答您的问题,我认为在定义类的主要功能时使用接口没有任何优势。

于 2012-07-04T10:21:08.917 回答
2

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

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

另一方面,存储库的具体实现驻留在基础设施层(或测试存储库的测试模块中)。他们实现了上述存储库合约,但也有自己的持久性特定特征范围。例如,如果您使用 NHibernate 来持久化您的实体,那么拥有一个包含 NHibernate 会话和其他 NHibernate 相关通用管道的所有 NHibernate 存储库的超类可能会派上用场。

由于您不能继承多个类,因此您的最终具体存储库继承的这两件事之一必须是接口。

将域层契约作为一个接口 ( ISomeEntityRepository) 更合乎逻辑,因为它是一个纯粹的声明性抽象,并且不能对将使用什么底层持久性机制做出任何假设——即它不能实现任何东西。

特定于持久性的可以是一个抽象类(NHibernateRepositoryNHibernateRepository<T>在基础设施层中),它允许您将一些行为集中在那里,这些行为对于将存在的所有特定于持久性存储的存储库都是通用的。

这导致类似:

public class SomeEntityRepository : NHibernateRepository<SomeEntity>, ISomeEntityRepository 
{
  //...
}
于 2012-07-04T14:47:51.947 回答
2

虽然其他人可能需要添加更多内容,但从纯粹实用的角度来看,大多数 IoC 框架在接口 -> 类映射方面工作得更好。您可以在接口和类上拥有不同的可见性,而对于继承,可见性必须匹配。

如果您不使用 IoC 框架,那么在我看来没有区别。提供者基于抽象基类。

于 2012-07-04T10:07:43.157 回答
2

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

接口的结果始终是“这里没有恶作剧——所见即所得”,而抽象基类可能会产生副作用。

于 2012-07-04T10:13:13.540 回答
1

看看 Tim McCarthy 的 Repository Framework 的实现。< http://dddpds.codeplex.com/ >

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

所以你可以结合这两种方法来利用它们的优点。

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

于 2012-07-04T10:40:48.720 回答