Для чего конкретно используется интерфейс шаблона репозитория?

Я полностью понимаю идею дизайна шаблона репозитория. Но зачем нам реализовывать интерфейсный класс iDepository? Для чего конкретно это нужно? Сам класс репозитория работает без класса интерфейса.

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


person Trio Cheung    schedule 04.08.2013    source источник


Ответы (3)


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

  1. Это позволяет вам легко определить, что неудачные тесты вызваны бизнес-уровнем, а не уровнем репозитория;
  2. Это делает ваши тесты уровня бизнес-логики быстрыми, поскольку они не зависят ни от доступа к данным, который, как правило, медленный, ни от настройки структуры базы данных и тестовых данных, которые, как правило, очень медленные.

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

void Add(Noun noun);
int NumberOfNouns();

А это код вашего бизнес-класса:

public class BusinessClass {

    private IRepository _repository;

    public BusinessClass(IRepository repository) {
        _repository = repository;
    }

    // optionally, you can make your default constructor create an instance
    // of your default repository
    public BusinessClass() {
        _repository = new Repository();
    }

    // method which will be tested 
    public AddNoun(string noun) {
        _repository.Add(new Noun(noun));
    }
}

Чтобы протестировать AddNoun, не нуждаясь в реальном репозитории, вам нужно настроить тестовый двойник. Обычно вы делаете это, используя фиктивный фреймворк, такой как Moq, но я напишу фиктивный класс с нуля, просто чтобы проиллюстрировать концепцию.

public IRepository MockRepository : IRepository {
    private List<Noun> nouns = new List<Noun>();

    public void Add(Noun noun) {
        nouns.Add(noun);
    }

    public int NumberOfNouns() {
        return nouns.Count();
    }
}

Теперь одним из ваших тестов может быть это.

[Test]
public void AddingNounShouldIncreaseNounCountByOne() {
    // Arrange
    var mockRepository = new MockRepository();
    var businessClassToTest = new BusinessClass(mockRepository);

    // Act
    businessClassToTest.Add("cat");

    // Assert
    Assert.AreEqual(1, mockRepository.NumberOfNouns(), "Number of nouns in repository should have increased after calling AddNoun");

}

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

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

Удаление и повторное добавление тестовых данных обычно выполняется с помощью сценариев SQL, которые медленны и уязвимы при каждом изменении структуры базы данных. Поэтому рекомендуется ограничить использование базы данных только тестами самого репозитория и использовать макеты репозиториев при модульном тестировании других аспектов приложения.

Что касается использования абстрактных классов - да, это дало бы такую ​​же возможность предоставления тестовых двойников. Я не уверен, какой код вы бы выбрали для абстрактной базы, а какой конкретную реализацию. В этом ответе на вопрос SO есть интересное обсуждение абстрактного классы против интерфейсов.

person Polly Shaw    schedule 04.08.2013
comment
Как добиться 1 и 2? Я все еще не понимаю. Можете ли вы параллельно сравнить использование интерфейса с абстрактным классом? Фактически, интерфейс не позволяет реализовать метод, я считаю, что это пустая трата кода. - person Trio Cheung; 05.08.2013
comment
Я расширил модульное тестирование бизнес-уровня, используя тестовые двойники. - person Polly Shaw; 05.08.2013

Во-первых, вы должны понять, что такое шаблон репозитория. Это уровень абстракции, так что остальной части приложения не нужно заботиться о том, откуда берутся данные.

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

В качестве бонуса этот интерфейс также упрощает тестирование вашего приложения, поскольку вы можете легко макетировать интерфейс (или создать заглушку)

Интерфейс также позволяет вам развивать уровень данных. Например, вы можете начать с использования базы данных для всех классов репозитория. Но позже вы захотите переместить некоторую логику в веб-службу. Затем вам нужно только заменить репозиторий DB на репозиторий WCF. Вы также можете обнаружить, что репозиторий работает медленно, и хотите реализовать в нем простой кеш памяти (используя memcache или что-то еще).

person jgauffin    schedule 05.08.2013
comment
как насчет использования абстрактного класса? Я создаю класс репозитория для словарей, которые включают в себя разные языки, такие как английский, французский, итальянский, японский... Хотя каждый язык имеет очень разные свойства, у них есть некоторые общие свойства, такие как существительное, глагол, adj и т. д. Для некоторых функций, таких как GetEntry, будет то же самое, но GetPartofSpeech будет совершенно другим. Для сокращения кода я должен использовать интерфейс или абстрактный класс? - person Trio Cheung; 05.08.2013
comment
Сами сущности могут содержать что угодно. Цель репозитория состоит в том, чтобы создавать и наполнять эти объекты информацией. Следовательно, с точки зрения репозиториев не имеет значения, наследует ли объект базовый класс или нет. Это сам репозиторий, который должен реализовывать интерфейс. - person jgauffin; 05.08.2013

Я нашел очень полезную страницу msdn, демонстрирующую идею репозитория и разработки через тестирование. http://blogs.msdn.com/b/adonet/archive/2009/12/17/walkthrough-test-driven-development-with-the-entity-framework-4-0.aspx

person Trio Cheung    schedule 12.11.2013