Подтвердить что ты не робот

Преимущество использования интерфейса над абстрактным классом для шаблона репозитория?

Возможный дубликат:
Интерфейс vs Базовый класс

Его общий вид, чтобы увидеть шаблон хранилища, реализованный с использованием интерфейсов

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 - Выбор между классами и интерфейсами" можно перефразировать как "Выбирайте классы по интерфейсам, если нет веской причины не" - какие веские причины в конкретном случае шаблона репозитория

4b9b3361

Ответ 1

Основное преимущество использования интерфейса над абстрактным классом в этом случае заключается в том, что интерфейс полностью прозрачен: это больше проблема, когда у вас нет доступа к источнику класса, на который вы наследуете.

Однако эта прозрачность позволяет вам производить модульные тесты известной области: если вы тестируете класс, который принимает интерфейс в качестве параметра (используя метод инъекции зависимостей), вы знаете, что вы тестируете класс с известным количеством; реализация тестирования интерфейса будет содержать только ваш тестовый код.

Аналогично, при тестировании вашего репозитория вы знаете, что тестируете только свой код в репозитории. Это помогает ограничить количество возможных переменных/взаимодействий в тесте.

Ответ 2

Лично я имею тенденцию иметь интерфейс, который содержит подпись для методов, которые являются чисто "связанными с бизнесом", например, Foo GetFoo(), void DeleteFood(Foo foo) и т.д. У меня также есть общий абстрактный класс, который содержит защищенные методы, такие как T Get() или void Delete(T obj).

Я сохраняю свои методы в абстрактном классе Repository, чтобы внешний мир не знал о plumbery (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 заключается в том, что моим конкретным репозиториям не нужно внедрять метод, который им не нужен (например, Delete или Add), но они находятся в их распоряжении, если им это нужно. В текущем контексте для некоторых Foos нет причин для бизнеса, поэтому этот метод недоступен на интерфейсе.

Преимущество использования интерфейса над абстрактным классом для бизнес-модели заключается в том, что интерфейс предоставляет ответы на то, как имеет смысл манипулировать Foo с бизнес-стороны (имеет ли смысл удалить некоторые foos? некоторые? и т.п.). Также проще использовать этот интерфейс при модульном тестировании. Тезис Repository, который я использую, не может быть проверен модулем, поскольку он обычно тесно связан с базой данных. Он может быть протестирован только в тестируемой интеграции. Использование абстрактного класса для бизнес-целей моих репозиториев помешает мне использовать их в модульных тестах.

Ответ 3

Это общий вопрос, который относится к любой иерархии классов, а не только к репозиториям. С чистой точки зрения OO интерфейс и чистый абстрактный класс одинаковы.

Если ваш класс является частью общедоступного API, основным преимуществом использования абстрактного класса является то, что вы можете добавлять методы в будущем с небольшим риском нарушения существующих реализаций.

Некоторым людям также нравится определять интерфейс как "что-то, что может сделать класс", и базовый класс как "какой класс", и поэтому будут использовать только интерфейсы для периферийных возможностей и всегда определять основную функцию (например. репозиторий) как класс. Я не уверен, где я нахожусь на этом.

Чтобы ответить на ваш вопрос, я не думаю, что есть какое-то преимущество в использовании интерфейса, когда он определяет основную функцию класса.

Ответ 4

В то время как другим может быть больше, чтобы добавить, с чисто практической точки зрения, большинство инфраструктур IoC лучше работают с интерфейсами → сопоставлениями классов. Вы можете иметь разные видимости на своих интерфейсах и классах, тогда как с наследованием видимость должна соответствовать.

Если вы не используете рамки IoC, с моей точки зрения нет никакой разницы. Поставщики основаны на абстрактных базовых классах.

Ответ 5

Я предполагаю, что ключевым отличием будет то, что абстрактный класс может содержать частные свойства и методы, в которых интерфейс не может, поскольку это только простой контракт.

Результат - это интерфейс, который всегда здесь "нет", - то, что вы видите, это то, что вы получаете ", в то время как абстрактный базовый класс может допускать побочные эффекты.

Ответ 6

Так как шаблон возникает в Domain Driven Design, вот DDD-ответ:

Контракт репозитория обычно определяется на уровне домена. Это позволяет объектам в слоях Domain и Application манипулировать абстракциями репозиториев, не заботясь об их реальной реализации и лежащих в основе данных хранения, другими словами, быть ненаучным. Кроме того, мы часто хотим, чтобы конкретные условия были включены в контракты некоторых репозиториев (в дополнение к вашим vanilla Add(), GetById() и т.д.), Поэтому я предпочитаю форму ISomeEntityRepository, чем просто IRepository<SomeEntity> - мы будем почему они должны быть интерфейсами позже.

Конкретные реализации репозиториев, с другой стороны, находятся на уровне инфраструктуры (или в модуле Tests для тестовых репозиториев). Они реализуют вышеупомянутый договор репозитория, но также имеют свой собственный диапазон характеристик, характерных для сохранения. Например, если вы используете NHibernate для сохранения своих сущностей, наличие суперкласса для всех хранилищ NHibernate с сеансом NHibernate и других связанных с ней NHibernate-сантехников может оказаться полезным.

Поскольку вы не можете наследовать несколько классов, одна из этих двух вещей, которые ваш окончательный конкретный репозиторий наследует, должна быть интерфейсом.

Более логичным для контракта уровня домена является интерфейс (ISomeEntityRepository), поскольку он является чисто декларативной абстракцией и не должен делать каких-либо предположений о том, какой базовый механизм сохранения будет использоваться, т.е. он не должен ничего реализовывать.

Конкретный характер может быть абстрактным классом (NHibernateRepository или NHibernateRepository<T> на уровне инфраструктуры), который позволяет централизовать там некоторые поведения, которые являются общими для всего диапазона хранилищ, хранящихся в стойких хранилищах, которые будут существуют.

В результате получается что-то вроде:

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

Ответ 7

Взгляните на реализацию Репозитория Тима Маккарти. < http://dddpds.codeplex.com/ >

Он использует интерфейсы, такие как IRepository<T> для определения контрактов, но он также использует абстрактные классы, такие как RepositoryBase<T> или его SqlCeRepositoryBase < T >, который реализует IRepository<T>. Абстрактный базовый класс - это код для устранения большого дублирования кода. Репозиторий, специфичный для типа, просто должен наследовать от абстрактного базового класса и должен добавить код для своей цели. Пользователи API могут просто кодировать интерфейс по контракту.

Таким образом, вы можете комбинировать оба подхода, чтобы использовать их преимущества.

Кроме того, я думаю, что большинство IoC-Frameworks могут обрабатывать абстрактные классы.