diff --git a/docs/tasks/CompositeStudentSearch.md b/docs/tasks/CompositeStudentSearch.md new file mode 100644 index 0000000..8ab2a72 --- /dev/null +++ b/docs/tasks/CompositeStudentSearch.md @@ -0,0 +1,455 @@ +# Техническое задание: поиск студентов с использованием паттерна Компоновщик + +## Задача + +Реализовать механизм поиска студентов по диапазону значений с использованием паттерна **Компоновщик**. + +Должны поддерживаться следующие критерии поиска: + +- диапазон `groupNumber`; +- диапазон `averageGrade`; +- диапазон `recordBookNumber`. + +Критерии должны поддерживать объединение в составной критерий. + +Режим объединения критериев — **объединение (ИЛИ)** или **пересечение (И)** — задаётся в конструкторе основного класса. + +--- + +# Названия классов и интерфейсов + +## `StudentSearchCriteria` + +Интерфейс отдельного критерия поиска. + +### Контракт + +```java +boolean matches(Student student); +``` + +Метод возвращает `true`, если студент соответствует критерию. + +--- + +## `GroupNumberRangeCriteria` + +Критерий поиска по диапазону `groupNumber`. + +### Конструктор + +```java +GroupNumberRangeCriteria(String min, String max); +``` + +### Метод + +```java +boolean matches(Student student); +``` + +`groupNumber` имеет тип `String`, поэтому сравнение выполнять лексикографически. + +Границы диапазона включаются. + +--- + +## `AverageGradeRangeCriteria` + +Критерий поиска по диапазону `averageGrade`. + +### Конструктор + +```java +AverageGradeRangeCriteria(double min, double max); +``` + +### Метод + +```java +boolean matches(Student student); +``` + +Студент соответствует критерию, если: + +```text +min <= averageGrade <= max +``` + +--- + +## `RecordBookNumberRangeCriteria` + +Критерий поиска по диапазону `recordBookNumber`. + +### Конструктор + +```java +RecordBookNumberRangeCriteria(int min, int max); +``` + +### Метод + +```java +boolean matches(Student student); +``` + +Студент соответствует критерию, если: + +```text +min <= recordBookNumber <= max +``` + +--- + +# `CompositeStudentSearchCriteria` + +Компоновщик критериев поиска. + +Класс реализует: + +```java +StudentSearchCriteria +``` + +и содержит другие реализации `StudentSearchCriteria`. + +### Методы + +```java +void add(StudentSearchCriteria criteria); +``` + +```java +void remove(StudentSearchCriteria criteria); +``` + +```java +boolean matches(Student student); +``` + +### Логика + +`CompositeStudentSearchCriteria` должен поддерживать два режима: + +```text +UNION — объединение условий, ИЛИ +INTERSECTION — пересечение условий, И +``` + +Режим должен задаваться при создании компоновщика: + +```java +CompositeStudentSearchCriteria( + SearchOperation operation +); +``` + +где `SearchOperation` — перечисление: + +```java +public enum SearchOperation { + UNION, + INTERSECTION +} +``` + +Для `UNION` студент подходит, если выполняется **хотя бы один** вложенный критерий. + +Для `INTERSECTION` студент подходит, если выполняются **все** вложенные критерии. + +--- + +# `StudentSearchService` + +Основной класс поиска. + +Он должен принимать интерфейс `StudentSearchCriteria`, а не конкретную реализацию критерия. + +### Конструктор + +```java +StudentSearchService(StudentSearchCriteria criteria); +``` + +### Метод + +```java +MyList find(MyList students); +``` + +### Поведение + +Метод `find()` должен: + +1. Последовательно проверить студентов исходной коллекции. +2. Передать каждого студента в: + +```java +criteria.matches(student) +``` + +1. Добавить подходящих студентов в новую `MyList`. +2. Вернуть новую коллекцию. + +`StudentSearchService` не должен содержать логику конкретных диапазонов или объединения критериев. + +### Обработка отсутствующего критерия + +Если `StudentSearchService` создан без критерия: + +```java +new StudentSearchService(null); +``` + +необходимо выбросить: + +```java +IllegalArgumentException +``` + +Класс не должен выполнять поиск без заданного критерия. + +--- + +# Пример использования + +### Один критерий + +```java +StudentSearchCriteria criteria = + new AverageGradeRangeCriteria(4.0, 5.0); + +StudentSearchService service = + new StudentSearchService(criteria); + +MyList result = + service.find(students); +``` + +### Пересечение критериев + +Например, студент должен одновременно: + +- находиться в группе от `A10` до `C30`; +- иметь средний балл от `4.0` до `5.0`. + +```java +CompositeStudentSearchCriteria criteria = + new CompositeStudentSearchCriteria( + SearchOperation.INTERSECTION + ); + +criteria.add( + new GroupNumberRangeCriteria("A10", "C30") +); + +criteria.add( + new AverageGradeRangeCriteria(4.0, 5.0) +); + +StudentSearchService service = + new StudentSearchService(criteria); +``` + +### Объединение критериев + +Студент должен соответствовать хотя бы одному условию: + +```java +CompositeStudentSearchCriteria criteria = + new CompositeStudentSearchCriteria( + SearchOperation.UNION + ); + +criteria.add( + new AverageGradeRangeCriteria(4.5, 5.0) +); + +criteria.add( + new RecordBookNumberRangeCriteria(10000, 20000) +); + +StudentSearchService service = + new StudentSearchService(criteria); +``` + +--- + +# Общие требования + +1. `StudentSearchService` должен работать только через `StudentSearchCriteria`. +2. `StudentSearchService` не должен знать о конкретных критериях. +3. `CompositeStudentSearchCriteria` должен реализовывать тот же интерфейс `StudentSearchCriteria`. +4. Должна поддерживаться вложенность компоновщиков. + +Например: + +```text +Composite + ├── GroupNumberRangeCriteria + └── Composite + ├── AverageGradeRangeCriteria + └── RecordBookNumberRangeCriteria +``` + +1. Исходную `MyList` не изменять. +2. Результат возвращать в виде новой `MyList`. +3. Порядок найденных студентов должен сохраняться. +4. Стандартные `List`, `ArrayList`, `LinkedList` не использовать. +5. Готовые методы поиска не использовать. +6. При отсутствии совпадений возвращать пустую `MyList`. +7. Для диапазонов `min > max` выбрасывать `IllegalArgumentException`. + +--- + +# Примеры тестов + +## `GroupNumberRangeCriteria` + +Проверить: + +- значение внутри диапазона; +- значение равное нижней границе; +- значение равное верхней границе; +- значение вне диапазона. + +## `AverageGradeRangeCriteria` + +Проверить: + +```text +3.9 → false +4.0 → true +4.5 → true +5.0 → true +5.1 → false +``` + +## `RecordBookNumberRangeCriteria` + +Проверить значения внутри и вне диапазона, включая обе границы. + +## Некорректный диапазон + +Для каждого критерия проверить: + +```text +min > max +``` + +Ожидается: + +```java +IllegalArgumentException +``` + +## `INTERSECTION` + +Создать два критерия и проверить: + +```text +критерий A = true +критерий B = true +→ true +``` + +```text +критерий A = true +критерий B = false +→ false +``` + +## `UNION` + +Проверить: + +```text +A = true +B = false +→ true +``` + +```text +A = false +B = true +→ true +``` + +```text +A = false +B = false +→ false +``` + +## Компоновщик без критериев + +Проверить поведение: + +```java +new CompositeStudentSearchCriteria( + SearchOperation.INTERSECTION +); +``` + +и: + +```java +new CompositeStudentSearchCriteria( + SearchOperation.UNION +); +``` + +Поведение должно быть явно определено и закреплено тестами. + +Рекомендуется: + +- пустое `INTERSECTION` → `true`; +- пустое `UNION` → `false`. + +## `StudentSearchService` + +Проверить: + +- корректный поиск по одному критерию; +- пустой результат; +- пустую исходную коллекцию; +- сохранение порядка; +- неизменность исходной коллекции; +- работу с `CompositeStudentSearchCriteria`. + +## Проверка вложенного Компоновщика + +Создать компоновщик с вложенным компоновщиком и проверить корректность вычисления результата. + +## Проверка отсутствующего критерия + +Создать: + +```java +new StudentSearchService(null); +``` + +Проверить `IllegalArgumentException`. + +## Результат + +Реализовать: + +```text +StudentSearchCriteria.java +GroupNumberRangeCriteria.java +AverageGradeRangeCriteria.java +RecordBookNumberRangeCriteria.java +CompositeStudentSearchCriteria.java +SearchOperation.java +StudentSearchService.java +``` + +## Git + +Название ветки: + +```text +feature/composite-student-search +``` diff --git a/docs/tasks/CsvStudentIO.md b/docs/tasks/CsvStudentIO.md new file mode 100644 index 0000000..89e2ad5 --- /dev/null +++ b/docs/tasks/CsvStudentIO.md @@ -0,0 +1,324 @@ +# Техническое задание: чтение и дозапись `Student` в CSV + +## Задача + +Реализовать два независимых класса для работы с объектами `Student` в формате CSV: + +- `StudentCsvWriter` — дозапись студентов в файл; +- `StudentCsvReader` — последовательное чтение студентов из файла. + +Для работы с форматом CSV использовать готовую библиотеку для работы с CSV. Самостоятельно реализовывать парсинг CSV запрещено. + +Оба класса должны принимать **абсолютный путь к файлу**. + +Исключения `Student` и `StudentBuilder` не обрабатывать. + +--- + +# Формат CSV-файла + +Первая строка файла должна содержать заголовок: + +```text +groupNumber,averageGrade,recordBookNumber +``` + +Каждая следующая строка содержит данные одного студента: + +```text +groupNumber,averageGrade,recordBookNumber +``` + +Пример: + +```text +groupNumber,averageGrade,recordBookNumber +A12,4.5,10001 +B07,3.8,10002 +C25,4.9,10003 +``` + +Для формирования и чтения CSV использовать стандартную библиотеку для работы с форматом CSV. + +--- + +# `StudentCsvWriter` + +## Назначение + +Дозаписывать одного студента в указанный CSV-файл. + +## Контракт + +Конструктор: + +```java +public StudentCsvWriter(String absoluteFilePath) +``` + +Метод: + +```java +public void write(Student student) throws IOException +``` + +### Требования + +1. Метод принимает одного `Student`. +2. Запись выполняется в режиме добавления. +3. Существующие записи не изменяются. +4. Если файл существует, заголовок повторно не записывается. +5. Если файл не существует, необходимо создать файл. +6. При создании нового файла имя должно соответствовать формату: + +```text +students-yyyy-MM-dd-HH-mm.csv +``` + +Например: + +```text +students-2026-08-28-20-45.csv +``` + +1. Новый файл создаётся в директории, указанной абсолютным путём. +2. При создании нового файла сначала записывается заголовок. +3. При успешной записи метод завершается без исключения. +4. Метод не возвращает значение. +5. Исключения, связанные с записью файла, не обрабатывать внутри метода — они должны передаваться вызывающему коду. +6. Исключения `Student` и `StudentBuilder` также не обрабатывать. + +## Невозможность записи + +Если студент не может быть записан, например: + +- отсутствуют права на запись; +- невозможно создать файл; +- файл недоступен; +- произошла ошибка файловой системы; + +метод должен выбросить соответствующее исключение, например `IOException`. + +Запись не должна возвращать специальное значение вроде `0` для обозначения ошибки. + +--- + +# `StudentCsvReader` + +## Назначение + +Последовательно читать студентов из CSV-файла. + +## Контракт + +Класс должен реализовывать: + +```java +Iterator +``` + +Конструктор: + +```java +public StudentCsvReader(String absoluteFilePath) +``` + +Методы: + +```java +@Override +public boolean hasNext() +``` + +```java +@Override +public Student next() +``` + +### Требования к `hasNext()` + +1. Проверяет наличие следующей записи `Student`. +2. Заголовок CSV не считается записью. +3. Повторный вызов `hasNext()` не должен пропускать следующую запись. +4. После окончания файла метод возвращает `false`. + +### Требования к `next()` + +1. Возвращает следующего `Student`. +2. `Student` создаётся через существующий `StudentBuilder`. +3. При отсутствии следующего элемента выбрасывается: + +```text +NoSuchElementException +``` + +1. Весь файл не должен предварительно загружаться в коллекцию. + +## Невозможность прочитать студента + +Если следующую запись невозможно прочитать или преобразовать в `Student`, например: + +- некорректный формат CSV; +- отсутствует обязательное поле; +- значение невозможно преобразовать в нужный тип; +- данные не проходят валидацию `Student`; + +исключение должно передаваться вызывающему коду. + +Исключения `Student` и `StudentBuilder` **не перехватывать и не обрабатывать**. + +Некорректные записи не должны молча пропускаться. + +--- + +# Общие требования + +1. Оба класса принимают абсолютный путь к файлу. +2. Использовать готовую библиотеку для работы с CSV. +3. Не реализовывать собственный CSV-парсер. +4. Не использовать стандартные коллекции Java как основное хранилище студентов. +5. Не дублировать в этих классах валидацию `Student`. +6. Не дублировать логику создания `Student` вне `StudentBuilder`. +7. `StudentCsvWriter` отвечает только за запись. +8. `StudentCsvReader` отвечает только за чтение. + +--- + +# Примеры тестов + +## `StudentCsvWriter` + +### Создание нового файла + +Передать путь к несуществующему файлу и вызвать: + +```java +writer.write(student); +``` + +Проверить: + +- файл создан; +- имя соответствует `students-yyyy-MM-dd-HH-mm.csv`; +- присутствует заголовок; +- студент записан; +- метод завершился без исключения. + +### Дозапись + +Создать файл с одним студентом и выполнить: + +```java +writer.write(student2); +``` + +Проверить, что: + +- первая запись сохранилась; +- `student2` добавлен в конец; +- заголовок присутствует только один раз. + +### Несколько последовательных записей + +```java +writer.write(student1); +writer.write(student2); +writer.write(student3); +``` + +Проверить наличие трёх записей в правильном порядке. + +### Невозможность записи + +Передать путь, по которому невозможно создать или открыть файл для записи. + +Проверить, что `write()` выбрасывает `IOException`. + +Не проверять специальное возвращаемое значение — метод ничего не возвращает. + +--- + +## `StudentCsvReader` + +### Чтение корректного файла + +Для файла: + +```text +groupNumber,averageGrade,recordBookNumber +A12,4.5,10001 +B07,3.8,10002 +C25,4.9,10003 +``` + +Проверить: + +- `hasNext()` возвращает `true` до последнего студента; +- `next()` возвращает студентов в правильном порядке; +- после последнего студента `hasNext()` возвращает `false`. + +### Повторный вызов `hasNext()` + +```java +reader.hasNext(); +reader.hasNext(); +Student student = reader.next(); +``` + +Проверить, что первый студент не был пропущен. + +### `next()` после окончания файла + +После чтения всех студентов вызвать: + +```java +reader.next(); +``` + +Проверить выброс: + +```text +NoSuchElementException +``` + +### Некорректная запись + +Передать файл: + +```text +groupNumber,averageGrade,recordBookNumber +A12,4.5,10001 +INVALID +C25,4.9,10003 +``` + +Проверить, что ошибка при чтении некорректной записи передаётся вызывающему коду. + +### Некорректные данные `Student` + +Передать данные, нарушающие ограничения `Student`. + +Проверить, что исключение от `Student` или `StudentBuilder` выходит наружу и не перехватывается `StudentCsvReader`. + +--- + +# Названия файлов + +```text +StudentCsvWriter.java +StudentCsvReader.java +``` + +# Git + +Название ветки: + +```text +feature/csv-student-io +``` + +Conventional Commit: + +```text +feat: add csv student reader and writer +``` diff --git a/docs/tasks/CustomList.md b/docs/tasks/CustomList.md new file mode 100644 index 0000000..e95066e --- /dev/null +++ b/docs/tasks/CustomList.md @@ -0,0 +1,44 @@ +# Техническое задание: кастомный контейнер List + +## Задача + +Реализовать собственный контейнер для хранения объектов, не используя готовые реализации `List`, `ArrayList` и `LinkedList`. + +## Требования + +1. Создать интерфейс `MyList`. + +2. В интерфейсе определить основные операции: + + - `add(...)`; + - `get(...)`; + - `set(...)`; + - `remove(...)`; + - `size()`; + - `isEmpty()`. + +3. Создать класс `MyLinkedList`, реализующий `MyList`. + +4. Реализовать контейнер на основе собственных узлов `Node`. + +5. Контейнер должен поддерживать: + + - добавление элемента; + - получение элемента по индексу; + - замену элемента по индексу; + - удаление элемента по индексу; + - получение количества элементов; + - проверку на пустоту. + +6. Обработать некорректные индексы с помощью исключений. + +7. Контейнер не должен зависеть от класса `Student`, сортировки или пользовательского интерфейса. + +## Результат + +Должны быть реализованы: + +```text +MyList.java +MyLinkedList.java +``` diff --git a/docs/tasks/EvenOnlySortStrategy.md b/docs/tasks/EvenOnlySortStrategy.md new file mode 100644 index 0000000..306c38c --- /dev/null +++ b/docs/tasks/EvenOnlySortStrategy.md @@ -0,0 +1,368 @@ +# Техническое задание: `EvenOnlySortStrategy` + +## Задача + +Создать класс `EvenOnlySortStrategy`, реализующий дополнительное задание по сортировке элементов с чётным значением выбранного числового поля. + +Класс должен использовать **паттерн Стратегия**: он не реализует собственный алгоритм сортировки, а делегирует сортировку переданной реализации `SortStrategy`. + +## Контракт + +Класс: + +```java +public class EvenOnlySortStrategy implements SortStrategy +``` + +Должен содержать два конструктора: + +```java +public EvenOnlySortStrategy() +``` + +```java +public EvenOnlySortStrategy(SortStrategy sortStrategy) +``` + +Также реализовать метод: + +```java +public void setSortStrategy(SortStrategy sortStrategy) +``` + +и основной метод интерфейса `SortStrategy`: + +```java +@Override +public void sort( + MyList list, + Comparator comparator +) +``` + +Для определения чётности числового поля использовать: + +```java +ToIntFunction +``` + +Поэтому рекомендуется также передавать функцию получения числового значения через конструктор или отдельный метод настройки. + +Для `Student` в качестве функции использовать: + +```java +Student::getRecordBookNumber +``` + +## Поведение при отсутствии стратегии + +Если `sortStrategy` не задан: + +```java +EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>(); +``` + +то вызов: + +```java +strategy.sort(students, comparator); +``` + +должен завершаться контролируемой ошибкой. + +Рекомендуется выбрасывать: + +```java +IllegalStateException +``` + +с понятным сообщением о том, что стратегия сортировки не задана. + +Не следует использовать `null` как признак успешного выполнения сортировки. + +## Поведение `setSortStrategy` + +Метод: + +```java +setSortStrategy(SortStrategy sortStrategy) +``` + +заменяет текущую стратегию. + +После вызова метода последующая сортировка должна выполняться именно переданной стратегией. + +Передача `null` должна приводить к контролируемой ошибке, например: + +```java +IllegalArgumentException +``` + +## Основная бизнес-логика + +`EvenOnlySortStrategy` **не должен содержать код какого-либо алгоритма сортировки**. + +Запрещено реализовывать внутри класса: + +- Merge Sort; +- Bubble Sort; +- Selection Sort; +- другие алгоритмы сортировки; +- вызовы стандартных методов сортировки Java. + +Класс отвечает только за: + +1. определение элементов, участвующих в специальной сортировке; +2. сохранение исходных позиций нечётных элементов; +3. передачу подходящих элементов переданной `SortStrategy`; +4. восстановление результата на исходные позиции; +5. делегирование самого алгоритма сортировки. + +Таким образом: + +```text +EvenOnlySortStrategy + │ + ├── определяет, какие элементы сортировать + │ + ├── передаёт сортируемые элементы + │ ↓ + │ SortStrategy + │ ↓ + │ MergeSortStrategy + │ QuickSortStrategy + │ ... + │ + └── возвращает результат на исходные позиции +``` + +Один `EvenOnlySortStrategy` должен работать с **любой реализацией `SortStrategy`**. + +## Рекомендуемый алгоритм + +Рекомендуется использовать следующий подход. + +Исходная коллекция: + +```text +[7, 8, 3, 2, 5, 4] +``` + +При выборе `recordBookNumber`: + +```text +нечётные: 7, 3, 5 +чётные: 8, 2, 4 +``` + +1. Пройти исходный `MyList`. +2. С помощью `ToIntFunction` определить чётность каждого элемента. +3. Запомнить позиции элементов с чётным значением. +4. Сформировать отдельный `MyList` только из чётных элементов. +5. Передать эту коллекцию в `sortStrategy.sort(...)`. +6. Полученные отсортированные элементы последовательно вернуть на сохранённые позиции. +7. Элементы с нечётными значениями не изменять. + +Например, переданная стратегия `MergeSortStrategy` сортирует: + +```text +[8, 2, 4] +``` + +в: + +```text +[2, 4, 8] +``` + +После возвращения элементов на исходные позиции: + +```text +[7, 2, 3, 4, 5, 8] +``` + +При этом `EvenOnlySortStrategy` не знает, каким способом были отсортированы `8`, `2`, `4`. + +## Работа с любой `SortStrategy` + +Должно быть возможно использовать: + +```java +EvenOnlySortStrategy strategy = + new EvenOnlySortStrategy<>( + new MergeSortStrategy<>(), + Student::getRecordBookNumber + ); +``` + +А также после создания заменить стратегию: + +```java +strategy.setSortStrategy(new AnotherSortStrategy<>()); +``` + +После этого специальная сортировка должна использовать новую стратегию без изменения `EvenOnlySortStrategy`. + +## Требования к `ToIntFunction` + +Функция получения числового значения должна передаваться извне. + +Для `Student`: + +```java +Student::getRecordBookNumber +``` + +Стратегия не должна напрямую обращаться к: + +```java +student.getRecordBookNumber() +``` + +или к другим полям `Student`. + +Это позволяет использовать класс с другими типами объектов и другими числовыми полями. + +## Требования к коллекциям + +Работать необходимо с: + +```java +MyList +``` + +Использование `ArrayList`, `LinkedList` и других стандартных коллекций для хранения элементов запрещено. + +Вспомогательная коллекция чётных элементов также должна быть `MyList`. + +## Тесты + +### Переданная стратегия используется + +Передать `MergeSortStrategy`. + +Проверить, что сортировка чётных элементов выполняется переданной стратегией. + +### Замена стратегии + +1. Создать `EvenOnlySortStrategy` с одной реализацией `SortStrategy`. +2. Выполнить сортировку. +3. Вызвать `setSortStrategy(...)`. +4. Выполнить сортировку повторно. +5. Проверить, что используется новая стратегия. + +### Конструктор без стратегии + +Создать: + +```java +new EvenOnlySortStrategy<>(); +``` + +Вызвать `sort()`. + +Проверить выброс `IllegalStateException`. + +### Конструктор со стратегией + +Создать объект с `MergeSortStrategy`. + +Проверить успешное выполнение сортировки. + +### Передача `null` в `setSortStrategy` + +Проверить выброс `IllegalArgumentException`. + +### Все элементы нечётные + +```text +[7, 3, 5, 1] +``` + +Результат должен полностью совпадать с исходной коллекцией. + +### Все элементы чётные + +```text +[8, 2, 6, 4] +``` + +Ожидаемый результат: + +```text +[2, 4, 6, 8] +``` + +### Смешанные значения + +```text +[7, 8, 3, 2, 5, 4] +``` + +Ожидаемый результат: + +```text +[7, 2, 3, 4, 5, 8] +``` + +### Повторяющиеся значения + +Проверить корректность сортировки при наличии одинаковых чётных значений. + +### Один элемент + +Проверить отдельно чётный и нечётный элемент. + +### Пустая коллекция + +Сортировка не должна приводить к ошибке. + +### Проверка сохранения нечётных элементов + +Проверить не только значения, но и то, что объекты с нечётным `recordBookNumber` остались на тех же индексах. + +### Проверка разных реализаций + +Использовать `EvenOnlySortStrategy` с каждой доступной реализацией `SortStrategy`. + +Проверить, что результат специальной сортировки соответствует контракту независимо от конкретного алгоритма. + +### Проверка делегирования + +Создать тестовую реализацию `SortStrategy`, которая фиксирует факт вызова `sort()`. + +Передать её в `EvenOnlySortStrategy`. + +Проверить, что: + +- `sort()` был вызван; +- в стратегию передана коллекция только с чётными элементами; +- сама `EvenOnlySortStrategy` не выполняет сортировку самостоятельно. + +## Результат + +Реализовать: + +```text +EvenOnlySortStrategy.java +``` + +Класс должен зависеть от абстракции: + +```text +SortStrategy +MyList +Comparator +ToIntFunction +``` + +и **не должен содержать реализации конкретного алгоритма сортировки**. + +## Git + +Ветка: + +```text +feature/even-only-sort-strategy +``` diff --git a/docs/tasks/Sorting.md b/docs/tasks/Sorting.md new file mode 100644 index 0000000..3fc39b0 --- /dev/null +++ b/docs/tasks/Sorting.md @@ -0,0 +1,48 @@ +# Техническое задание: сортировка слиянием + +## Задача + +Реализовать паттерн Strategy и собственный алгоритм сортировки слиянием объектов `Student`. + +## Требования + +1. Создать интерфейс `SortStrategy` с методом: + +```text +sort(MyList list, Comparator comparator) +``` + +1. Создать класс `MergeSortStrategy`, реализующий `SortStrategy`. + +2. Реализовать алгоритм сортировки слиянием самостоятельно. + +3. Готовые методы сортировки Java не использовать. + +4. Сортировка должна работать с `MyList`. + +5. Сортировка должна использовать переданный `Comparator`. + +6. Реализовать сортировку `Student` по каждому из трёх полей: + + - `groupNumber`; + - `averageGrade`; + - `recordBookNumber`. + +7. Создать отдельные компараторы: + + - `StudentGroupComparator`; + - `StudentAverageGradeComparator`; + - `StudentRecordBookComparator`. + +8. `MergeSortStrategy` не должен зависеть от класса `Student`. + +## Результат + +```text +SortStrategy.java +MergeSortStrategy.java + +StudentGroupComparator.java +StudentAverageGradeComparator.java +StudentRecordBookComparator.java +``` diff --git a/docs/tasks/StreamCollection.md b/docs/tasks/StreamCollection.md new file mode 100644 index 0000000..e447a58 --- /dev/null +++ b/docs/tasks/StreamCollection.md @@ -0,0 +1,327 @@ +# Техническое задание: заполнение `MyList` посредством Stream API + +## Задача + +Реализовать механизм заполнения собственной коллекции `MyList` посредством Java Stream API без использования стандартных коллекций Java в качестве промежуточного или итогового хранилища. + +## Требования + +1. Итоговым результатом работы потока должна быть: + +```java +MyList +``` + +1. Для сбора элементов потока непосредственно в `MyList` реализовать собственный `Collector`. + +2. Стандартные коллекции Java (`List`, `ArrayList`, `LinkedList` и другие реализации `List`) запрещено использовать: + + - как итоговую коллекцию; + - как промежуточное хранилище элементов перед их переносом в `MyList`. + +3. `Student` должен создаваться только через уже существующий независимый класс `StudentBuilder`, если объект создаётся первоначально. + +4. `MyListCollector` не должен создавать или изменять объекты `Student`. Он работает с уже созданными объектами. + +5. При использовании `map` для получения нового `Student` использовать методы `with...()`, а не `StudentBuilder`. + +6. Логика заполнения `MyList` должна быть отделена от логики класса `Student`. + +7. Для добавления объектов использовать существующий метод: + +```java +add(Student student) +``` + +1. Необходимо реализовать поддержку объединения частичных результатов, необходимую для работы `Collector`. + +--- + +## `MyListCollector` + +Создать универсальный класс: + +```java +public class MyListCollector + implements Collector, MyList> { +} +``` + +Реализовать методы: + +```text +supplier() +accumulator() +combiner() +finisher() +characteristics() +``` + +Назначение: + +- `supplier()` — создать пустой `MyList`; +- `accumulator()` — добавить очередной элемент в `MyList`; +- `combiner()` — объединить две частично заполненные коллекции; +- `finisher()` — вернуть готовый `MyList`; +- `characteristics()` — определить характеристики сборщика. + +Коллектор должен работать с `Student` без специальной логики, связанной с его полями. + +--- + +## `MyCollectors` + +Создать вспомогательный класс: + +```java +public final class MyCollectors { + + public static Collector, MyList> toMyList() { + return new MyListCollector<>(); + } +} +``` + +Класс должен предоставлять удобный способ получения собственного сборщика. + +--- + +## Контракт использования + +Должна поддерживаться конструкция: + +```java +MyList students = Stream.of( + student1, + student2, + student3 +).collect(MyCollectors.toMyList()); +``` + +где `student1`, `student2`, `student3` — валидные объекты `Student`, предварительно созданные через `StudentBuilder`. + +Результатом должен быть именно `MyList`. + +--- + +## Пример `map` + +`map` должен использоваться для преобразования одного `Student` в новый `Student`. + +Исходный объект не изменяется. Для создания нового объекта использовать `with...()`. + +Одно из преобразуемых полей должно быть `String` и формироваться по заданному бизнес-правилу: + +```text +Заглавная латинская буква + двузначное число +``` + +Например: + +```text +A07 +B12 +Z35 +``` + +Пример: + +```java +MyList students = Stream.of( + student1, + student2, + student3 +) +.map(student -> student.withGroupNumber( + generateGroupNumber(student) +)) +.collect(MyCollectors.toMyList()); +``` + +Пример функции формирования значения: + +```java +private static String generateGroupNumber(Student student) { + int number = Math.abs(student.getRecordBookNumber().hashCode() % 100); + char letter = (char) ('A' + + Math.abs(student.getRecordBookNumber().hashCode()) % 26); + + return String.format("%c%02d", letter, number); +} +``` + +Результат `map` должен оставаться `Stream`, после чего он непосредственно собирается в `MyList`. + +Формат создаваемого `groupNumber` должен соответствовать: + +```text +^[A-Z][0-9]{2}$ +``` + +--- + +## Пример `filter` совместно с `map` + +Проверить совместную работу промежуточных операций: + +```java +MyList students = Stream.of( + student1, + student2, + student3 +) +.filter(student -> student.getAverageGrade() >= 4.0) +.map(student -> student.withGroupNumber( + generateGroupNumber(student) +)) +.collect(MyCollectors.toMyList()); +``` + +Проверить, что: + +- в результат попали только студенты со средним баллом не ниже `4.0`; +- каждый результат является новым `Student`; +- `groupNumber` соответствует формату `[A-Z][0-9]{2}`; +- исходные объекты не изменились. + +--- + +## Параллельные потоки + +`combiner()` должен корректно объединять две частично сформированные `MyList`. + +Реалистичный пример: + +```java +MyList students = IntStream.range(0, 10_000) + .parallel() + .mapToObj(i -> student1.withGroupNumber( + String.format("%c%02d", + 'A' + (i % 26), + i % 100) + )) + .collect(MyCollectors.toMyList()); +``` + +Проверить: + +- результат содержит `10_000` объектов; +- ни один объект не потерян; +- количество объектов не превышает ожидаемого; +- каждый `groupNumber` соответствует формату `[A-Z][0-9]{2}`; +- объекты `Student` не изменяются после создания; +- `combiner()` корректно объединяет частичные результаты. + +--- + +## Примеры тестов + +### Заполнение несколькими `Student` + +Создать три валидных объекта `Student` через `StudentBuilder` и собрать их: + +```java +MyList result = Stream.of( + student1, + student2, + student3 +).collect(MyCollectors.toMyList()); +``` + +Проверить: + +```text +result != null +result.size() == 3 +``` + +и соответствие элементов исходному потоку. + +### Проверка порядка + +Для: + +```text +student1 +student2 +student3 +``` + +проверить: + +```text +result.get(0) == student1 +result.get(1) == student2 +result.get(2) == student3 +``` + +### Пустой поток + +```java +MyList result = Stream.empty() + .collect(MyCollectors.toMyList()); +``` + +Проверить: + +```text +result != null +result.isEmpty() +``` + +### Проверка `map` + +Преобразовать несколько `Student` через `withGroupNumber()`. + +Проверить: + +- исходные объекты не изменились; +- созданные объекты отличаются от исходных; +- `groupNumber` соответствует формату `[A-Z][0-9]{2}`. + +### Проверка `filter` и `map` + +Проверить, что после `filter` и `map` в `MyList` попадают только необходимые студенты с корректно сформированным `groupNumber`. + +### Параллельный поток + +Создать поток из `10_000` объектов через `parallel()`. + +Проверить размер итоговой коллекции, отсутствие потери элементов и корректность `groupNumber`. + +--- + +## Ограничения + +Запрещено: + +- использовать `Collectors.toList()`; +- использовать `ArrayList`, `LinkedList` или другие стандартные коллекции для накопления результата; +- сначала собирать `Student` в стандартную коллекцию, а затем переносить их в `MyList`; +- создавать `Student` внутри `MyListCollector`; +- изменять существующий `Student`; +- использовать `StudentBuilder` внутри `map` вместо `with...()`. + +## Результат + +Реализовать: + +```text +MyListCollector.java +MyCollectors.java +``` + +Использовать существующие: + +```text +Student.java +StudentBuilder.java +MyList.java +MyLinkedList.java +``` + +### Рекомендация по архитектуре + +`MyListCollector` должен зависеть только от интерфейса `MyList`, а не от конкретной реализации `MyLinkedList`. + +`MyCollectors` должен содержать только методы создания собственных сборщиков и не должен содержать бизнес-логику. diff --git a/docs/tasks/Student.md b/docs/tasks/Student.md new file mode 100644 index 0000000..2e17172 --- /dev/null +++ b/docs/tasks/Student.md @@ -0,0 +1,46 @@ +# Техническое задание: класс `Student` + +## Задача + +Реализовать класс `Student` как единственную точку проверки корректности данных. Объект класса `Student` не может быть создан с некорректными данными. + +## Требования + +1. Класс `Student` содержит три поля: + + - `groupNumber`; + - `averageGrade`; + - `recordBookNumber`. + +2. Все поля должны быть `private` и неизменяемыми после создания объекта. + +3. Проверка корректности данных должна находиться в классе `Student`. + +4. Реализовать класс `StudentBuilder` для создания объектов `Student`. + +5. В `StudentBuilder` реализовать методы: + + - `setGroupNumber(...)`; + - `setAverageGrade(...)`; + - `setRecordBookNumber(...)`; + - `build()`. + +6. Метод `build()` должен создавать только корректный объект `Student`. При некорректных данных должно выбрасываться исключение. + +7. Реализовать для `Student`: + + - методы получения значений полей; + - `toString()`; + - `equals()`; + - `hashCode()`. + +## Результат + +Должны быть реализованы два независимых класса: + +```text +Student.java +StudentBuilder.java +``` + +Классы не должны зависеть от сортировки, коллекции, меню и ввода данных. diff --git a/docs/tasks/StudentOccurrenceIndexFinder.md b/docs/tasks/StudentOccurrenceIndexFinder.md new file mode 100644 index 0000000..0c6f202 --- /dev/null +++ b/docs/tasks/StudentOccurrenceIndexFinder.md @@ -0,0 +1,688 @@ +# Техническое задание: многопоточный поиск индексов `Student` + +## Задача + +Реализовать многопоточный поиск всех вхождений заданного объекта `Student` в `MyList`. + +Метод **не должен выводить никаких сообщений в консоль**. + +Результатом работы должен быть массив `int[]`, содержащий индексы всех элементов исходной коллекции, которые равны переданному `Student`. + +Сравнение выполнять через существующий `Student.equals()`. + +--- + +## Входная информация + +Основной метод принимает: + +```java +MyList students +``` + +Исходная коллекция студентов. + +```java +Student target +``` + +Студент, вхождения которого необходимо найти. + +--- + +## Выходная информация + +Метод возвращает: + +```java +int[] +``` + +Массив должен содержать индексы всех совпавших элементов. + +Пример: + +```text +Исходная коллекция: + +[Student A, Student B, Student A, Student C, Student A] + +Результат: + +[0, 2, 4] +``` + +Индексы должны быть расположены в порядке возрастания. + +При отсутствии совпадений: + +```text +[] +``` + +При пустой коллекции: + +```text +[] +``` + +--- + +# Название класса + +```text +StudentOccurrenceIndexFinder +``` + +--- + +# Публичные методы и контракты + +## `findOccurrences` + +```java +public int[] findOccurrences( + MyList students, + Student target +); +``` + +Метод должен: + +1. Проверить входные параметры. +2. Разделить коллекцию на диапазоны индексов. +3. Передать каждый диапазон отдельной задаче. +4. Выполнить поиск в нескольких потоках. +5. Получить от каждого потока найденные индексы. +6. Объединить результаты. +7. Вернуть индексы в порядке возрастания. +8. Не изменять исходную коллекцию. +9. Не изменять объекты `Student`. +10. Не выводить ничего в консоль. + +--- + +# Вспомогательные методы + +Количество и вид `private` методов могут быть выбраны разработчиком. + +Рекомендуется выделить: + +```java +private int calculateThreadCount(int size); +``` + +Определяет количество рабочих потоков. + +```java +private int countChunkSize(int size, int threadCount); +``` + +Определяет размер диапазона для одной задачи. + +```java +private MyList findInRange( + MyList students, + Student target, + int fromIndex, + int toIndex +); +``` + +Обрабатывает диапазон: + +```text +[fromIndex, toIndex) +``` + +и возвращает индексы найденных совпадений. + +--- + +# Работа с многопоточностью + +## Рекомендуемый подход + +Не использовать общий контейнер результатов, к которому одновременно обращаются несколько потоков. + +Также **не рекомендуется использовать мьютексы** (`synchronized`, `Lock`, `ReentrantLock`) для хранения результата. + +Лучше использовать модель: + +```text + MyList + │ + ┌────────────┼────────────┐ + ↓ ↓ ↓ + Поток 1 Поток 2 Поток 3 + диапазон диапазон диапазон + │ │ │ + ↓ ↓ ↓ + MyList MyList MyList + └────────────┼────────────┘ + ↓ + основной поток + ↓ + merge + ↓ + int[] +``` + +Каждый поток создаёт **собственный контейнер `MyList`** и записывает найденные индексы только в него. + +Это позволяет избежать состояния гонки без синхронизации общей структуры. + +--- + +# Разбиение коллекции на потоки + +Количество потоков рекомендуется определять на основании: + +```java +Runtime.getRuntime().availableProcessors() +``` + +Количество потоков не должно превышать количество элементов: + +```text +threadCount = min( + availableProcessors(), + students.size() +) +``` + +### Пример + +Для коллекции из `10000` элементов и четырёх рабочих потоков: + +```text +Поток 1 → [0, 2500) +Поток 2 → [2500, 5000) +Поток 3 → [5000, 7500) +Поток 4 → [7500, 10000) +``` + +Каждый индекс должен принадлежать **ровно одному диапазону**. + +Не допускаются: + +- пропущенные индексы; +- обработка одного индекса несколькими потоками. + +--- + +# Поиск внутри потока + +Каждая задача обрабатывает только свой диапазон. + +Для каждого индекса: + +```java +Student student = students.get(index); +``` + +затем: + +```java +target.equals(student) +``` + +Если результат `true`, индекс добавляется в локальный: + +```java +MyList +``` + +Пример: + +```text +Поток 1 → [0, 8, 15] +Поток 2 → [21, 24] +Поток 3 → [30, 42, 48] +Поток 4 → [63] +``` + +--- + +# Объединение результатов + +Основной поток после завершения всех задач должен получить локальные результаты и объединить их. + +Рекомендуется передавать задачи в порядке возрастания диапазонов: + +```text +Результат 1 → диапазон 0 +Результат 2 → диапазон 1 +Результат 3 → диапазон 2 +... +``` + +Если каждая задача добавляет индексы в возрастающем порядке, а результаты объединяются в порядке диапазонов, итоговый массив уже будет отсортирован: + +```text +[0, 8, 15, 21, 24, 30, 42, 48, 63] +``` + +В этом случае дополнительная сортировка результата не требуется. + +**`Arrays.sort()` и другие готовые методы сортировки использовать не следует**, поскольку порядок можно гарантировать самим алгоритмом объединения. + +--- + +# Потокобезопасность + +## Мьютексы + +Использование мьютексов для результата **не требуется**. + +Не рекомендуется использовать: + +```java +synchronized +``` + +```java +Lock +``` + +```java +ReentrantLock +``` + +```java +AtomicInteger +``` + +для общего результата. + +Причина: потоки не должны изменять одну общую структуру. + +Каждый поток работает со своим: + +```text +MyList +``` + +а объединение выполняется после завершения параллельной части одним потоком. + +--- + +# Рекомендуемые классы и импорты + +Для реализации рекомендуется использовать стандартные средства Java: + +```java +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +``` + +Рекомендуемая схема: + +```text +ExecutorService + ↓ +Callable> + ↓ +Future> +``` + +Каждый `Callable` обрабатывает свой диапазон и возвращает собственный `MyList`. + +После получения всех `Future` основной метод объединяет результаты. + +После завершения работы `ExecutorService` должен быть корректно закрыт. + +При `InterruptedException` необходимо восстановить статус прерывания текущего потока: + +```java +Thread.currentThread().interrupt(); +``` + +--- + +# Использование `MyList` + +Сторонние контейнеры **не допускаются**. + +Запрещено использовать: + +```text +ArrayList +LinkedList +Vector +CopyOnWriteArrayList +ConcurrentLinkedQueue +и другие стандартные контейнеры +``` + +как хранилище промежуточных или итоговых результатов. + +Для результатов каждого потока использовать: + +```java +MyList +``` + +Итог преобразовать в: + +```java +int[] +``` + +--- + +# Требуется ли изменять `MyList` + +Текущего интерфейса **достаточно для реализации задачи**: + +```java +public interface MyList { + + void add(T element); + + T get(int index); + + T set(int index, T element); + + T remove(int index); + + int size(); + + boolean isEmpty(); +} +``` + +Дополнительные методы добавлять **не требуется**. + +Для задачи достаточно: + +```java +size() +get(index) +add(index) +``` + +Поскольку поиск выполняется только на чтение, а результаты каждого потока добавляются в его собственный `MyList`, существующего интерфейса достаточно. + +### Важно + +Метод: + +```java +void add(T element); +``` + +должен добавлять элемент в конец коллекции. + +Это позволяет каждому потоку формировать свои индексы в порядке их обнаружения. + +--- + +# Использование `Student` + +Изменения в `Student` **не требуются**. + +Использовать существующий метод: + +```java +equals(Object obj) +``` + +Сравнение выполнять: + +```java +target.equals(students.get(index)) +``` + +Не использовать: + +```text +== +``` + +Не сравнивать объекты вручную по отдельным полям. + +Не использовать `toString()` для определения совпадения. + +--- + +# `StudentBuilder` + +Изменения в `StudentBuilder` **не требуются**. + +Он не участвует в алгоритме поиска. + +--- + +# Обработка входных данных + +Если: + +```java +students == null +``` + +выбросить: + +```java +IllegalArgumentException +``` + +Если: + +```java +target == null +``` + +выбросить: + +```java +IllegalArgumentException +``` + +Для пустой коллекции вернуть: + +```java +new int[0] +``` + +Без создания рабочих потоков. + +--- + +# Обобщённый алгоритм реализации + +```text +1. Проверить students и target. +2. Получить размер коллекции. +3. Если размер равен 0 → вернуть []. +4. Вычислить количество потоков. +5. Разделить индексы коллекции на непересекающиеся диапазоны. +6. Создать Callable для каждого диапазона. +7. Запустить задачи через ExecutorService. +8. Каждый поток: + - получает fromIndex и toIndex; + - проходит свой диапазон; + - сравнивает Student через equals(); + - записывает найденные индексы в собственный MyList. +9. Основной поток получает результаты через Future. +10. Объединяет MyList в порядке диапазонов. +11. Преобразует результат в int[]. +12. Возвращает массив. +``` + +--- + +# Примеры тестов + +## Одно совпадение + +```text +[A, B, C, D] +``` + +Искомый: + +```text +C +``` + +Ожидается: + +```text +[2] +``` + +## Несколько совпадений + +```text +[A, B, A, C, A] +``` + +Ожидается: + +```text +[0, 2, 4] +``` + +## Нет совпадений + +```text +[A, B, C] +``` + +Искомый: + +```text +D +``` + +Ожидается: + +```text +[] +``` + +## Все элементы совпадают + +```text +[A, A, A, A] +``` + +Ожидается: + +```text +[0, 1, 2, 3] +``` + +## Пустая коллекция + +Ожидается: + +```text +[] +``` + +## Один элемент + +Проверить: + +```text +[A] + A → [0] +[A] + B → [] +``` + +## Проверка границ диапазонов потоков + +Проверить коллекции размеров: + +```text +1 +2 +3 +4 +10 +100 +1000 +``` + +Особое внимание уделить совпадениям: + +```text +на последнем элементе одного диапазона; +на первом элементе следующего диапазона. +``` + +Каждый индекс должен быть найден ровно один раз. + +## Проверка порядка результата + +Создать совпадения в разных диапазонах. + +Проверить, что результат имеет вид: + +```text +[0, 3, 7, 12, 25, 49, 100] +``` + +и находится в порядке возрастания без использования дополнительной сортировки. + +## Проверка `Student.equals()` + +Создать два разных экземпляра `Student` с одинаковыми значениями полей. + +Один поместить в коллекцию, второй передать как `target`. + +Проверить, что индекс считается совпадением согласно `equals()`. + +## Проверка неизменности коллекции + +После выполнения метода проверить: + +- размер `MyList`; +- порядок элементов; +- значения всех элементов. + +## Проверка отсутствия вывода + +Перехватить стандартный вывод и убедиться, что выполнение `findOccurrences()` не выводит сообщений в консоль. + +## Большая коллекция + +Создать коллекцию, например из `100000` студентов, с заранее известными индексами совпадений. + +Проверить: + +- все ожидаемые индексы найдены; +- лишних индексов нет; +- индексы идут по возрастанию; +- итоговый массив имеет правильный размер. + +## Git + +Название ветки: + +```text +feature/student-occurrence-index-finder +``` + +## Результат + +Необходимо реализовать: + +```text +StudentOccurrenceIndexFinder.java +``` + +Существующие интерфейсы и классы: + +```text +MyList.java +Student.java +StudentBuilder.java +``` + +изменять не требуется. diff --git a/docs/tasks/StudentUpdate.md b/docs/tasks/StudentUpdate.md new file mode 100644 index 0000000..e3db385 --- /dev/null +++ b/docs/tasks/StudentUpdate.md @@ -0,0 +1,326 @@ +# Техническое задание: обновление `Student` + +## Задача + +Обновить класс `Student` и связанный с ним код в соответствии с новой моделью данных и требованиями к неизменяемости объекта. + +Текущая реализация содержит: + +```java +private final String groupNumber; +private final double averageGrade; +private final String recordBookNumber; +``` + +Необходимо изменить тип `recordBookNumber` и добавить методы создания изменённых копий `Student`. + +## Изменения в `Student` + +### 1. Изменить тип `recordBookNumber` + +Поле: + +```java +private final String recordBookNumber; +``` + +заменить на: + +```java +private final int recordBookNumber; +``` + +Метод: + +```java +getRecordBookNumber() +``` + +должен возвращать `int`. + +Все проверки, `equals()`, `hashCode()` и `toString()` должны быть адаптированы под новый тип. + +### 2. Добавить бизнес-правило для `groupNumber` + +`groupNumber` должен соответствовать строго следующему формату: + +```text +<Заглавная буква латинского алфавита><2 цифры> +``` + +Регулярное выражение: + +```text +^[A-Z][0-9]{2}$ +``` + +Допустимые значения: + +```text +A12 +B32 +Z00 +``` + +Недопустимые значения: + +```text +а12 +a12 +A1 +A123 +AA1 +а1А +аа!!а123 +23фи!12 +№№###*() +``` + +Проверка должна выполняться при создании `Student`. + +`Student` не может существовать с некорректным `groupNumber`. + +### 3. Проверить `averageGrade` + +Сохранить правило: + +```text +0.0 <= averageGrade <= 5.0 +``` + +Значения `0.0` и `5.0` допустимы. + +### 4. Проверить `recordBookNumber` + +После преобразования в `int` установить бизнес-правило: + +```text +recordBookNumber > 0 +``` + +Значение `0` и отрицательные значения считаются недопустимыми. + +--- + +# Методы `with...()` + +Добавить методы, возвращающие **новый объект `Student`**, не изменяя текущий объект. + +Необходимо реализовать: + +```java +withGroupNumber(String groupNumber) +withAverageGrade(double averageGrade) +withRecordBookNumber(int recordBookNumber) +``` + +Каждый метод должен: + +1. Сохранять исходный объект без изменений. +2. Изменять только указанное поле в новом объекте. +3. Сохранять остальные поля исходного объекта. +4. Повторно применять все правила валидации. +5. Возвращать новый валидный объект `Student`. + +Пример: + +```java +Student updated = student.withAverageGrade(4.8); +``` + +После выполнения: + +```text +student.getAverageGrade() == прежнее значение +updated.getAverageGrade() == 4.8 +``` + +Если новое значение невалидно, метод должен выбросить исключение, а исходный объект не должен измениться. + +--- + +# `StudentBuilder` + +Обновить существующий `StudentBuilder` в соответствии с новой моделью `Student`. + +Методы должны соответствовать типам: + +```java +setGroupNumber(String groupNumber) +setAverageGrade(double averageGrade) +setRecordBookNumber(int recordBookNumber) +``` + +`build()` должен создавать только валидный `Student`. + +Все новые правила валидации должны применяться при создании объекта через `StudentBuilder`. + +--- + +# Обновление зависимого кода + +Найти и обновить весь код, использующий `Student` и изменяемые методы или типы. + +В частности, обновить: + +- `Comparator`; +- сортировки; +- источники данных; +- тестовые классы; +- создание `Student`; +- `with...()`-операции; +- чтение и запись данных, если они используют `recordBookNumber`. + +Например, существующий: + +```java +implements Comparator +``` + +должен использовать актуальный метод: + +```java +getAverageGrade() +``` + +вместо ошибочного `getAvarageGrade()`. + +Компаратор `recordBookNumber` должен сравнивать значения как числа. + +Например: + +```java +Integer.compare( + s1.getRecordBookNumber(), + s2.getRecordBookNumber() +) +``` + +Все места, где `recordBookNumber` рассматривался как `String`, необходимо адаптировать к `int`. + +--- + +# Тесты + +Обновить существующий `StudentTest` с учётом новой модели. + +## Создание корректного `Student` + +Проверить создание объекта со значениями: + +```text +groupNumber = A12 +averageGrade = 4.5 +recordBookNumber = 12345 +``` + +Проверить корректность всех геттеров. + +## Проверка `groupNumber` + +Проверить успешное создание для: + +```text +A12 +B32 +Z00 +``` + +Проверить исключение для: + +```text +a12 +A1 +A123 +AA1 +а1А +аа!!а123 +23фи!12 +№№###*() +``` + +## Проверка `averageGrade` + +Проверить: + +```text +0.0 — допустимо +5.0 — допустимо +-1.0 — недопустимо +6.0 — недопустимо +``` + +## Проверка `recordBookNumber` + +Проверить: + +```text +1 — допустимо +12345 — допустимо +0 — недопустимо +-1 — недопустимо +``` + +## Тесты `with...()` + +### `withGroupNumber()` + +Проверить, что: + +- возвращается новый объект; +- `groupNumber` изменён; +- `averageGrade` не изменился; +- `recordBookNumber` не изменился; +- исходный `Student` остался прежним. + +### `withAverageGrade()` + +Проверить те же свойства для `averageGrade`. + +### `withRecordBookNumber()` + +Проверить те же свойства для `recordBookNumber`. + +### `with...()` с невалидным значением + +Для каждого метода передать некорректное значение. + +Проверить: + +- выбрасывается `IllegalArgumentException`; +- исходный объект не изменяется; +- не создаётся невалидный `Student`. + +## `equals()` и `hashCode()` + +Обновить существующие тесты с учётом `int recordBookNumber`. + +Проверить: + +- одинаковые студенты равны; +- одинаковые студенты имеют одинаковый `hashCode`; +- изменение любого поля создаёт отличающийся объект. + +## Результат + +Необходимо обновить: + +```text +Student.java +StudentBuilder.java +StudentTest.java +``` + +а также **все классы проекта, зависящие от изменённого интерфейса `Student`**, включая компараторы и другие реализации, использующие `recordBookNumber`, `groupNumber`, `averageGrade` или их геттеры. + +### Основной принцип + +`Student` должен оставаться единственной точкой истины для валидности данных: + +```text +StudentBuilder ──┐ + ├──> Student +with...() ───────┘ +``` + +Ни `Builder`, ни `with...()`, ни внешние классы не должны позволять создать невалидный объект. diff --git a/docs/tasks/Testing.md b/docs/tasks/Testing.md new file mode 100644 index 0000000..26f898c --- /dev/null +++ b/docs/tasks/Testing.md @@ -0,0 +1,67 @@ +# Общее техническое задание: тестирование + +## Задача + +Для всех реализованных классов написать автоматические тесты с использованием **JUnit**. + +## Требования + +1. Для каждого основного класса проекта должны быть написаны тесты. + +2. Проверить: + + - корректное выполнение основных методов; + - корректную обработку некорректных данных; + - граничные случаи; + - ожидаемые исключения. + +3. Для `Student` проверить: + + - создание корректного объекта; + - невозможность создания объекта с некорректными данными; + - работу `Builder`; + - `equals()` и `hashCode()`. + +4. Для `MyLinkedList` проверить: + + - `add()`; + - `get()`; + - `set()`; + - `remove()`; + - `size()`; + - `isEmpty()`; + - некорректные индексы. + +5. Для `MergeSortStrategy` проверить: + + - сортировку по каждому из трёх полей; + - уже отсортированную коллекцию; + - обратный порядок; + - коллекцию из одного элемента; + - пустую коллекцию. + +6. Для каждого компаратора проверить правильное сравнение объектов `Student`. + +7. Тесты должны быть независимыми друг от друга и воспроизводимыми. + +8. Необходимо использовать JUnit и стандартные средства проверки результатов. + +## Результат + +Тесты размещать отдельно от основного кода, например: + +```text +src/ +├── main/ +│ └── java/ +└── test/ + └── java/ + ├── StudentTest.java + ├── MyLinkedListTest.java + ├── MergeSortStrategyTest.java + ├── StudentGroupComparatorTest.java + ├── StudentAverageGradeComparatorTest.java + └── StudentRecordBookComparatorTest.java +``` + +Все тесты должны успешно проходить перед слиянием ветки в `main`.