Найти в Дзене
"Мы"-Прогер

Изучаем C# - Пример ООП (часть 4)

Предыдущие статьи: В прошлой статье мы сделали CRUD-сервисы для работы с сущностями Person (человек) и DisabilitySheet (больничный). Теперь надо сделать CRUD-сервисы для AppointmentOrder (приказ о назначении) и HolidayOrder (приказ об отпуске). Поскольку у этих двух сущностей есть общие поля, мы создали для них общий базовый класс BaseOrder (базовый приказ). Поскольку иерархия CRUD-сервисов повторяет иерархию сущностей, то мы должны сначала сделать сервис для BaseOrder и уже потом - для этих двух сущностей. Сервис для BaseOrder будет работать с полями, общими для всех Order'ов. Поскольку у BaseOrderService будет несколько наследников для разных сущностей (AppointmentOrderService и HolidayOrderService), то мы должны сделать у него универсальные типы для сущностей и для фильтра, как это сделали в BaseEntityService: Конкретные значения для TEntity и TListFilter будут заданы в сервисах-наследниках AppointmentOrderService и HolidayOrderService. Красным горит ошибка "Abstract inherited membe
Оглавление

Предыдущие статьи:

В прошлой статье мы сделали CRUD-сервисы для работы с сущностями Person (человек) и DisabilitySheet (больничный). Теперь надо сделать CRUD-сервисы для AppointmentOrder (приказ о назначении) и HolidayOrder (приказ об отпуске).

Базовый сервис приказов

Поскольку у этих двух сущностей есть общие поля, мы создали для них общий базовый класс BaseOrder (базовый приказ). Поскольку иерархия CRUD-сервисов повторяет иерархию сущностей, то мы должны сначала сделать сервис для BaseOrder и уже потом - для этих двух сущностей. Сервис для BaseOrder будет работать с полями, общими для всех Order'ов.

Поскольку у BaseOrderService будет несколько наследников для разных сущностей (AppointmentOrderService и HolidayOrderService), то мы должны сделать у него универсальные типы для сущностей и для фильтра, как это сделали в BaseEntityService:

Конкретные значения для TEntity и TListFilter будут заданы в сервисах-наследниках AppointmentOrderService и HolidayOrderService.

Красным горит ошибка "Abstract inherited member ... is not implemented" ("Абстрактный унаследованный член класса не реализован"). Это значит, что в базовом классе был абстрактный метод FilterList(), который наш класс получил в силу наследования. Поскольку этот метод абстрактный, то мы должны либо написать его реализацию, либо пометить наш класс абстрактным. Подумаем, каким должен быть метод FilterList() для BaseOrder. Будет ли в нём какая-нибудь логика? По каким полям можно фильтровать все сущности, унаследованные от BaseOrder? У BaseOrder есть такие поля:

-2

Соответственно, мы можем фильтровать приказы по номеру, дате, сотруднику и автору. Значит, в нашем FilterList() нужно написать логику фильтрации по этим полям, а в FilterList() классов-наследников - по новым полям, которые есть у наследников. Значит, FilterList() в BaseOrderService нужен, но это будет не окончательная его реализация - он будет также и в сервисах-наследниках AppointmentOrderService и HolidayOrderService.

Чтобы сделать метод, можно набрать его вручную, а можно поставить каретку для ввода текста на красную ошибку, нажать лампочку и выбрать "Implement missing members":

-3
-4

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

Наследование фильтров

Поскольку иерархия наследования сервисов повторяет иерархию наследования сущностей, логично сделать такую же иерархию наследования и для фильтров. Таким образом, мы создадим класс BaseOrderListFilter и унаследуем от него классы AppointmentOrderListFilter и HolidayOrderListFilter:

-5

Отметим, что у каждого приказа есть одна "Дата" Date, но мы хотим фильтровать приказы по промежутку дат - "Дата с" DateFrom и "Дата по" DateTo, поэтому в фильтре даты две.

Классы AppointmentOrderListFilter и HolidayOrderListFilter будут от него наследоваться:

-6

У приказа о назначении есть Date от базового приказа, а также есть собственные StartDate / EndDate. Поэтому в фильтре будет два промежутка: один для Date из базового класса BaseOrderListFilter, второй для StartDate / EndDate в AppointmentOrderListFilter. Такая же история в HolidayOrderListFilter:

-7

Базовый сервис приказов (продолжение)

Итак, все фильтры для приказов наследуются от BaseOrderListFilter. Теперь мы можем наложить ограничение на TListFilter и продолжить написание BaseOrderService:

-8
-9

Фильтрацию по дате и автору приказа напишите самостоятельно.

Сервис приказов о назначении

Итак, BaseOrderService готов. Теперь напишем AppointmentOrderService. Он будет наследоваться от BaseOrderService:

-10

Метод FilterList() класса BaseOrderService фильтрует только по тем полям, которые есть в базовом фильтре приказов. Но фильтр приказов о назначении имеет дополнительно к этому поля Position ("Должность"), StartDate, EndDate, и надо фильтровать по ним. Эту логику нужно написать в методе FilterList(), который нужно переопределить. Для переопределения метода в Rider нажмите Ctrl + O (от слова "override"). Наберите в нём такой код:

-11

Первым делом мы фильтруем по должности Position. Далее мы фильтруем по промежутку дат: промежуток StartDate - EndDate у приказа должен пересекаться с промежутком StartDate - EndDate фильтра (подчёркнуто жёлтым). Эти неравенства - хитрый логический трюк, который вам стоит запомнить. Кроме того, EndDate у приказа может быть null, и в таком случае приказ должен считаться бессрочным и проходить фильтрацию, поэтому мы проверяем x.EndDate == null (подчёркнуто красным). Между условиями стоит оператор "ИЛИ", потому что мы хотим найти приказы, которые бессрочны ИЛИ попадают в промежуток по дате окончания.

Мы хотим вызвать сначала FilterList() базового сервиса приказов, а затем FilterList() сервиса приказов о назначении. Они дополняют друг друга. Но сейчас каждый из них выполняет всю работу отдельно - берёт список Entities, фильтрует его и превращает в List. Так как каждый из двух методов сам берёт список Entities в качестве отправной точки, то мы не можем сделать так, чтобы сначала фильтровал список один из методов, а потом другой. Поэтому придётся вынести отправную точку за пределы методов FilterList(), в метод GetList(). Эти изменения затронут самый базовый сервис BaseEntityService:

До
До
После
После

Но теперь FilterList() принимает два аргумента, а не один, как было раньше. Придётся поменять все места использования FilterList() во всех сервисах. Чтобы автоматизировать этот процесс, уберём входной аргумент IList<TEntity> list, написанный вручную, нажмём правой кнопкой на название метода и выберем Refactor - Change Signature... В списке входных параметров добавим нужный:

-14

Нажмём Next. Выберем, как именно модифицировать места вызова метода - автоматически подставить ту переменную, которая имеет нужный тип данных IList<TEntity>:

-15

Откроем, например, PersonService. Нажмём на синюю полоску на полях, чтобы посмотреть изменения. Второй входной аргумент был дописан автоматически и имеет правильный тип данных IList<Person>:

-16

Соответственно, в DisabilitySheetService он имеет тип IList<DisabilitySheet>, а в BaseOrderService - IList<TEntity> (то есть зависит от конкретного сервиса-наследника). Не забудем поправить код внутри FilterList(), чтобы он использовал в качестве изначального списка не Entities, а наш новый list.

Теперь в AppointmentOrderService переопределим GetList(), чтобы он сначала применял фильтр из BaseOrderService, а затем из AppointmentOrderService (чтобы можно было переопределить GetList(), нужно пометить его как virtual):

-17

Всё готово. Пробуйте!

Сервис приказов об отпуске напишите сами.

Далее

Оглавление

Изучаем C# с нуля - Очень краткий курс - Оглавление
"Мы"-Прогер27 января