Ранее мы начали делать большой пример объектно-ориентированного программирования:
Теперь допишем его.
CRUD-сервис
Сделаем в нашем PersonService методы Create / Read / Update / Delete ("Создать / Считать / Обновить / Удалить"). Начнём по порядку с Create():
Поскольку метод Create должен создавать человека, он получает его на вход. Мы сначала проверяем, не существует ли уже этот человек, по Id. Если такого человека нет, то метод Persons.FirstOrDefault() вернёт null и мы зайдём в if, где будет кинуто исключение.
Иначе мы добавляем человека в нашу импровизированную "базу данных", которую представляет список Persons. Чтобы избежать некоторых трудностей с IEnumerable, надо сменить тип списка с IEnumerable<Person> на IList<Person>:
Чтобы сохранить принцип абстракции в действии, мы используем интерфейс IList вместо самого List - потом можно будет заменить список на что-нибудь другое, а тип IList и все места его использования останутся.
Далее метод Read():
Он похож Create(), только здесь исключение кидается, если человек не найден, а там было условие "если найден". Метод получает id человека, которого мы хотим найти, и выдаёт найденного человека.
Метод Update() служит для сохранения изменений:
Здесь мы сначала находим человека, сохранённого в базе данных, с таким же id. Затем мы находим его индекс и заменяем в списке элемент с таким индексом. Такая логика требует двукратного прохода по списку: для поиска элемента и для поиска индекса. Чтобы избежать этого, напишем метод расширения для списка, который будет выдавать индекс элемента, найденного по лямбде.
Метод расширения для списка с универсальным типом
Создадим папку CommonExtensions и в ней файл ListExtensions:
Напомню, метод расширения создаётся как статический метод в статическом классе и у первого его входного аргумента, к которому будет применяться метод расширения, ставится ключевое слово this.
В нашем случае список может содержать не только людей, но и всё что угодно, поэтому для типа данных списка мы заводим универсальный тип <T>. Собственно, метод расширения применяется к списку, состоящему из элементов типа T, то есть, к IList<T>. Это первый входной аргумент.
Второй входной аргумент - это условие, по которому мы будем искать элемент в списке. Условие представляет собой функцию, принимающую на вход элемент списка (тип данных T) и выдающую в ответ логическое значение (тип данных bool). Поэтому такая функция имеет тип данных Func<T, bool>.
Функция проверки условия вызывается на каждом элементе списка по порядку. Если она возвращает true, то мы выдаём текущий индекс в ответ. По синтаксису, мы обязаны что-либо возвращать во всех ветвях кода, в том числе если мы прошли весь список и ничего не нашли. Если мы забудем это сделать, то C# предупредит нас об этом красной ошибкой. В этом случае мы выдаём в ответ -1.
Поскольку метод расширения написан в отдельном namespace, то чтобы метод был виден, необходимо подключить его namespace. Для этого при наборе кода в месте вызова метода необходимо выбрать правильный вариант из подсказки и нажать Enter, либо дописать using HelloWorld.ServiceExample.CommonExtensions; вручную:
Теперь метод Update() будет выглядеть так:
Метод Delete() напишите самостоятельно. Найдите в интернете, как удалить элемент из списка.
Юнит и модульное тестирование
Юнит-тестирование - это когда мы тестируем работу каждого метода по отдельности. При этом мы должны не забывать протестировать все возможные случаи, в том числе кидание исключений. Этим мы занимались в предыдущей статье.
Модульное тестирование - это когда мы тестируем сразу целый программный модуль (например, PersonService). Иногда без этого нельзя. Например, как проверить, что новый человек действительно добавился в базу данных? Нужно после Create() вызвать Read() с его Id:
Debug.Assert() выдаёт ошибку, если условие ложно. Здесь мы используем хитрость, чтобы убедиться, что все поля класса Person были сохранены как надо - мы считываем его из базы и сравниваем распечатки. Поскольку мы переопределили стандартный метод ToString() у Person, то он печатает все нужные нам поля.
Есть ещё интеграционное тестирование - тесты, которые проверяют, как взаимодействуют между собой разные модули программы.
Перехват исключений с помощью try - catch
А как проверить, что исключение кидается? Ведь в этом случае нам нужно, чтобы программа не падала, если исключение есть, и упала, если исключения нет. То есть, всё наоборот. Для этого перехватим исходное исключение в блоке catch и напечатаем в консоль, что всё хорошо. А если исключения не возникнет, то выполнение дойдёт до конца блока try и мы кинем новое исключение, которое уже не будет перехвачено, потому что имеет другой тип:
Отметим также, что для дублирующихся данных (фамилия человека) необходимо заводить отдельные переменные. Иначе можно забыть поменять фамилию в одном тесте, поменяв её в другом, и тесты сломаются.
Вообще, для тестирования приложений существуют целые фреймворки - наборы написанных за нас библиотек, которые можно подключить к проекту и использовать.
Протестируйте остальные методы самостоятельно.
Далее
Далее мы продолжим делать наши сервисы и применим там наследование с универсальным типом.
Оглавление: