Merge branch 'docs/tasks'
This commit is contained in:
@@ -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<Student> find(MyList<Student> students);
|
||||
```
|
||||
|
||||
### Поведение
|
||||
|
||||
Метод `find()` должен:
|
||||
|
||||
1. Последовательно проверить студентов исходной коллекции.
|
||||
2. Передать каждого студента в:
|
||||
|
||||
```java
|
||||
criteria.matches(student)
|
||||
```
|
||||
|
||||
1. Добавить подходящих студентов в новую `MyList<Student>`.
|
||||
2. Вернуть новую коллекцию.
|
||||
|
||||
`StudentSearchService` не должен содержать логику конкретных диапазонов или объединения критериев.
|
||||
|
||||
### Обработка отсутствующего критерия
|
||||
|
||||
Если `StudentSearchService` создан без критерия:
|
||||
|
||||
```java
|
||||
new StudentSearchService(null);
|
||||
```
|
||||
|
||||
необходимо выбросить:
|
||||
|
||||
```java
|
||||
IllegalArgumentException
|
||||
```
|
||||
|
||||
Класс не должен выполнять поиск без заданного критерия.
|
||||
|
||||
---
|
||||
|
||||
# Пример использования
|
||||
|
||||
### Один критерий
|
||||
|
||||
```java
|
||||
StudentSearchCriteria criteria =
|
||||
new AverageGradeRangeCriteria(4.0, 5.0);
|
||||
|
||||
StudentSearchService service =
|
||||
new StudentSearchService(criteria);
|
||||
|
||||
MyList<Student> 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<Student>` не изменять.
|
||||
2. Результат возвращать в виде новой `MyList<Student>`.
|
||||
3. Порядок найденных студентов должен сохраняться.
|
||||
4. Стандартные `List`, `ArrayList`, `LinkedList` не использовать.
|
||||
5. Готовые методы поиска не использовать.
|
||||
6. При отсутствии совпадений возвращать пустую `MyList<Student>`.
|
||||
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
|
||||
```
|
||||
@@ -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<Student>
|
||||
```
|
||||
|
||||
Конструктор:
|
||||
|
||||
```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
|
||||
```
|
||||
@@ -0,0 +1,44 @@
|
||||
# Техническое задание: кастомный контейнер List
|
||||
|
||||
## Задача
|
||||
|
||||
Реализовать собственный контейнер для хранения объектов, не используя готовые реализации `List`, `ArrayList` и `LinkedList`.
|
||||
|
||||
## Требования
|
||||
|
||||
1. Создать интерфейс `MyList<T>`.
|
||||
|
||||
2. В интерфейсе определить основные операции:
|
||||
|
||||
- `add(...)`;
|
||||
- `get(...)`;
|
||||
- `set(...)`;
|
||||
- `remove(...)`;
|
||||
- `size()`;
|
||||
- `isEmpty()`.
|
||||
|
||||
3. Создать класс `MyLinkedList<T>`, реализующий `MyList<T>`.
|
||||
|
||||
4. Реализовать контейнер на основе собственных узлов `Node<T>`.
|
||||
|
||||
5. Контейнер должен поддерживать:
|
||||
|
||||
- добавление элемента;
|
||||
- получение элемента по индексу;
|
||||
- замену элемента по индексу;
|
||||
- удаление элемента по индексу;
|
||||
- получение количества элементов;
|
||||
- проверку на пустоту.
|
||||
|
||||
6. Обработать некорректные индексы с помощью исключений.
|
||||
|
||||
7. Контейнер не должен зависеть от класса `Student`, сортировки или пользовательского интерфейса.
|
||||
|
||||
## Результат
|
||||
|
||||
Должны быть реализованы:
|
||||
|
||||
```text
|
||||
MyList.java
|
||||
MyLinkedList.java
|
||||
```
|
||||
@@ -0,0 +1,368 @@
|
||||
# Техническое задание: `EvenOnlySortStrategy`
|
||||
|
||||
## Задача
|
||||
|
||||
Создать класс `EvenOnlySortStrategy`, реализующий дополнительное задание по сортировке элементов с чётным значением выбранного числового поля.
|
||||
|
||||
Класс должен использовать **паттерн Стратегия**: он не реализует собственный алгоритм сортировки, а делегирует сортировку переданной реализации `SortStrategy`.
|
||||
|
||||
## Контракт
|
||||
|
||||
Класс:
|
||||
|
||||
```java
|
||||
public class EvenOnlySortStrategy<T> implements SortStrategy<T>
|
||||
```
|
||||
|
||||
Должен содержать два конструктора:
|
||||
|
||||
```java
|
||||
public EvenOnlySortStrategy()
|
||||
```
|
||||
|
||||
```java
|
||||
public EvenOnlySortStrategy(SortStrategy<T> sortStrategy)
|
||||
```
|
||||
|
||||
Также реализовать метод:
|
||||
|
||||
```java
|
||||
public void setSortStrategy(SortStrategy<T> sortStrategy)
|
||||
```
|
||||
|
||||
и основной метод интерфейса `SortStrategy`:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void sort(
|
||||
MyList<T> list,
|
||||
Comparator<T> comparator
|
||||
)
|
||||
```
|
||||
|
||||
Для определения чётности числового поля использовать:
|
||||
|
||||
```java
|
||||
ToIntFunction<T>
|
||||
```
|
||||
|
||||
Поэтому рекомендуется также передавать функцию получения числового значения через конструктор или отдельный метод настройки.
|
||||
|
||||
Для `Student` в качестве функции использовать:
|
||||
|
||||
```java
|
||||
Student::getRecordBookNumber
|
||||
```
|
||||
|
||||
## Поведение при отсутствии стратегии
|
||||
|
||||
Если `sortStrategy` не задан:
|
||||
|
||||
```java
|
||||
EvenOnlySortStrategy<Student> strategy =
|
||||
new EvenOnlySortStrategy<>();
|
||||
```
|
||||
|
||||
то вызов:
|
||||
|
||||
```java
|
||||
strategy.sort(students, comparator);
|
||||
```
|
||||
|
||||
должен завершаться контролируемой ошибкой.
|
||||
|
||||
Рекомендуется выбрасывать:
|
||||
|
||||
```java
|
||||
IllegalStateException
|
||||
```
|
||||
|
||||
с понятным сообщением о том, что стратегия сортировки не задана.
|
||||
|
||||
Не следует использовать `null` как признак успешного выполнения сортировки.
|
||||
|
||||
## Поведение `setSortStrategy`
|
||||
|
||||
Метод:
|
||||
|
||||
```java
|
||||
setSortStrategy(SortStrategy<T> 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<T>` определить чётность каждого элемента.
|
||||
3. Запомнить позиции элементов с чётным значением.
|
||||
4. Сформировать отдельный `MyList<T>` только из чётных элементов.
|
||||
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<Student> strategy =
|
||||
new EvenOnlySortStrategy<>(
|
||||
new MergeSortStrategy<>(),
|
||||
Student::getRecordBookNumber
|
||||
);
|
||||
```
|
||||
|
||||
А также после создания заменить стратегию:
|
||||
|
||||
```java
|
||||
strategy.setSortStrategy(new AnotherSortStrategy<>());
|
||||
```
|
||||
|
||||
После этого специальная сортировка должна использовать новую стратегию без изменения `EvenOnlySortStrategy`.
|
||||
|
||||
## Требования к `ToIntFunction`
|
||||
|
||||
Функция получения числового значения должна передаваться извне.
|
||||
|
||||
Для `Student`:
|
||||
|
||||
```java
|
||||
Student::getRecordBookNumber
|
||||
```
|
||||
|
||||
Стратегия не должна напрямую обращаться к:
|
||||
|
||||
```java
|
||||
student.getRecordBookNumber()
|
||||
```
|
||||
|
||||
или к другим полям `Student`.
|
||||
|
||||
Это позволяет использовать класс с другими типами объектов и другими числовыми полями.
|
||||
|
||||
## Требования к коллекциям
|
||||
|
||||
Работать необходимо с:
|
||||
|
||||
```java
|
||||
MyList<T>
|
||||
```
|
||||
|
||||
Использование `ArrayList`, `LinkedList` и других стандартных коллекций для хранения элементов запрещено.
|
||||
|
||||
Вспомогательная коллекция чётных элементов также должна быть `MyList<T>`.
|
||||
|
||||
## Тесты
|
||||
|
||||
### Переданная стратегия используется
|
||||
|
||||
Передать `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<T>
|
||||
MyList<T>
|
||||
Comparator<T>
|
||||
ToIntFunction<T>
|
||||
```
|
||||
|
||||
и **не должен содержать реализации конкретного алгоритма сортировки**.
|
||||
|
||||
## Git
|
||||
|
||||
Ветка:
|
||||
|
||||
```text
|
||||
feature/even-only-sort-strategy
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
# Техническое задание: сортировка слиянием
|
||||
|
||||
## Задача
|
||||
|
||||
Реализовать паттерн Strategy и собственный алгоритм сортировки слиянием объектов `Student`.
|
||||
|
||||
## Требования
|
||||
|
||||
1. Создать интерфейс `SortStrategy<T>` с методом:
|
||||
|
||||
```text
|
||||
sort(MyList<T> list, Comparator<T> comparator)
|
||||
```
|
||||
|
||||
1. Создать класс `MergeSortStrategy<T>`, реализующий `SortStrategy<T>`.
|
||||
|
||||
2. Реализовать алгоритм сортировки слиянием самостоятельно.
|
||||
|
||||
3. Готовые методы сортировки Java не использовать.
|
||||
|
||||
4. Сортировка должна работать с `MyList<T>`.
|
||||
|
||||
5. Сортировка должна использовать переданный `Comparator<T>`.
|
||||
|
||||
6. Реализовать сортировку `Student` по каждому из трёх полей:
|
||||
|
||||
- `groupNumber`;
|
||||
- `averageGrade`;
|
||||
- `recordBookNumber`.
|
||||
|
||||
7. Создать отдельные компараторы:
|
||||
|
||||
- `StudentGroupComparator`;
|
||||
- `StudentAverageGradeComparator`;
|
||||
- `StudentRecordBookComparator`.
|
||||
|
||||
8. `MergeSortStrategy` не должен зависеть от класса `Student`.
|
||||
|
||||
## Результат
|
||||
|
||||
```text
|
||||
SortStrategy.java
|
||||
MergeSortStrategy.java
|
||||
|
||||
StudentGroupComparator.java
|
||||
StudentAverageGradeComparator.java
|
||||
StudentRecordBookComparator.java
|
||||
```
|
||||
@@ -0,0 +1,327 @@
|
||||
# Техническое задание: заполнение `MyList<Student>` посредством Stream API
|
||||
|
||||
## Задача
|
||||
|
||||
Реализовать механизм заполнения собственной коллекции `MyList<Student>` посредством Java Stream API без использования стандартных коллекций Java в качестве промежуточного или итогового хранилища.
|
||||
|
||||
## Требования
|
||||
|
||||
1. Итоговым результатом работы потока должна быть:
|
||||
|
||||
```java
|
||||
MyList<Student>
|
||||
```
|
||||
|
||||
1. Для сбора элементов потока непосредственно в `MyList<Student>` реализовать собственный `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<T>`
|
||||
|
||||
Создать универсальный класс:
|
||||
|
||||
```java
|
||||
public class MyListCollector<T>
|
||||
implements Collector<T, MyList<T>, MyList<T>> {
|
||||
}
|
||||
```
|
||||
|
||||
Реализовать методы:
|
||||
|
||||
```text
|
||||
supplier()
|
||||
accumulator()
|
||||
combiner()
|
||||
finisher()
|
||||
characteristics()
|
||||
```
|
||||
|
||||
Назначение:
|
||||
|
||||
- `supplier()` — создать пустой `MyList<T>`;
|
||||
- `accumulator()` — добавить очередной элемент в `MyList<T>`;
|
||||
- `combiner()` — объединить две частично заполненные коллекции;
|
||||
- `finisher()` — вернуть готовый `MyList<T>`;
|
||||
- `characteristics()` — определить характеристики сборщика.
|
||||
|
||||
Коллектор должен работать с `Student` без специальной логики, связанной с его полями.
|
||||
|
||||
---
|
||||
|
||||
## `MyCollectors`
|
||||
|
||||
Создать вспомогательный класс:
|
||||
|
||||
```java
|
||||
public final class MyCollectors {
|
||||
|
||||
public static <T> Collector<T, MyList<T>, MyList<T>> toMyList() {
|
||||
return new MyListCollector<>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Класс должен предоставлять удобный способ получения собственного сборщика.
|
||||
|
||||
---
|
||||
|
||||
## Контракт использования
|
||||
|
||||
Должна поддерживаться конструкция:
|
||||
|
||||
```java
|
||||
MyList<Student> students = Stream.of(
|
||||
student1,
|
||||
student2,
|
||||
student3
|
||||
).collect(MyCollectors.toMyList());
|
||||
```
|
||||
|
||||
где `student1`, `student2`, `student3` — валидные объекты `Student`, предварительно созданные через `StudentBuilder`.
|
||||
|
||||
Результатом должен быть именно `MyList<Student>`.
|
||||
|
||||
---
|
||||
|
||||
## Пример `map`
|
||||
|
||||
`map` должен использоваться для преобразования одного `Student` в новый `Student`.
|
||||
|
||||
Исходный объект не изменяется. Для создания нового объекта использовать `with...()`.
|
||||
|
||||
Одно из преобразуемых полей должно быть `String` и формироваться по заданному бизнес-правилу:
|
||||
|
||||
```text
|
||||
Заглавная латинская буква + двузначное число
|
||||
```
|
||||
|
||||
Например:
|
||||
|
||||
```text
|
||||
A07
|
||||
B12
|
||||
Z35
|
||||
```
|
||||
|
||||
Пример:
|
||||
|
||||
```java
|
||||
MyList<Student> 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<Student>`, после чего он непосредственно собирается в `MyList<Student>`.
|
||||
|
||||
Формат создаваемого `groupNumber` должен соответствовать:
|
||||
|
||||
```text
|
||||
^[A-Z][0-9]{2}$
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Пример `filter` совместно с `map`
|
||||
|
||||
Проверить совместную работу промежуточных операций:
|
||||
|
||||
```java
|
||||
MyList<Student> 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<Student>`.
|
||||
|
||||
Реалистичный пример:
|
||||
|
||||
```java
|
||||
MyList<Student> 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<Student> 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<Student> result = Stream.<Student>empty()
|
||||
.collect(MyCollectors.toMyList());
|
||||
```
|
||||
|
||||
Проверить:
|
||||
|
||||
```text
|
||||
result != null
|
||||
result.isEmpty()
|
||||
```
|
||||
|
||||
### Проверка `map`
|
||||
|
||||
Преобразовать несколько `Student` через `withGroupNumber()`.
|
||||
|
||||
Проверить:
|
||||
|
||||
- исходные объекты не изменились;
|
||||
- созданные объекты отличаются от исходных;
|
||||
- `groupNumber` соответствует формату `[A-Z][0-9]{2}`.
|
||||
|
||||
### Проверка `filter` и `map`
|
||||
|
||||
Проверить, что после `filter` и `map` в `MyList<Student>` попадают только необходимые студенты с корректно сформированным `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<T>`, а не от конкретной реализации `MyLinkedList<T>`.
|
||||
|
||||
`MyCollectors` должен содержать только методы создания собственных сборщиков и не должен содержать бизнес-логику.
|
||||
@@ -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
|
||||
```
|
||||
|
||||
Классы не должны зависеть от сортировки, коллекции, меню и ввода данных.
|
||||
@@ -0,0 +1,688 @@
|
||||
# Техническое задание: многопоточный поиск индексов `Student`
|
||||
|
||||
## Задача
|
||||
|
||||
Реализовать многопоточный поиск всех вхождений заданного объекта `Student` в `MyList<Student>`.
|
||||
|
||||
Метод **не должен выводить никаких сообщений в консоль**.
|
||||
|
||||
Результатом работы должен быть массив `int[]`, содержащий индексы всех элементов исходной коллекции, которые равны переданному `Student`.
|
||||
|
||||
Сравнение выполнять через существующий `Student.equals()`.
|
||||
|
||||
---
|
||||
|
||||
## Входная информация
|
||||
|
||||
Основной метод принимает:
|
||||
|
||||
```java
|
||||
MyList<Student> 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<Student> 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<Integer> findInRange(
|
||||
MyList<Student> students,
|
||||
Student target,
|
||||
int fromIndex,
|
||||
int toIndex
|
||||
);
|
||||
```
|
||||
|
||||
Обрабатывает диапазон:
|
||||
|
||||
```text
|
||||
[fromIndex, toIndex)
|
||||
```
|
||||
|
||||
и возвращает индексы найденных совпадений.
|
||||
|
||||
---
|
||||
|
||||
# Работа с многопоточностью
|
||||
|
||||
## Рекомендуемый подход
|
||||
|
||||
Не использовать общий контейнер результатов, к которому одновременно обращаются несколько потоков.
|
||||
|
||||
Также **не рекомендуется использовать мьютексы** (`synchronized`, `Lock`, `ReentrantLock`) для хранения результата.
|
||||
|
||||
Лучше использовать модель:
|
||||
|
||||
```text
|
||||
MyList<Student>
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
↓ ↓ ↓
|
||||
Поток 1 Поток 2 Поток 3
|
||||
диапазон диапазон диапазон
|
||||
│ │ │
|
||||
↓ ↓ ↓
|
||||
MyList<Integer> MyList<Integer> MyList<Integer>
|
||||
└────────────┼────────────┘
|
||||
↓
|
||||
основной поток
|
||||
↓
|
||||
merge
|
||||
↓
|
||||
int[]
|
||||
```
|
||||
|
||||
Каждый поток создаёт **собственный контейнер `MyList<Integer>`** и записывает найденные индексы только в него.
|
||||
|
||||
Это позволяет избежать состояния гонки без синхронизации общей структуры.
|
||||
|
||||
---
|
||||
|
||||
# Разбиение коллекции на потоки
|
||||
|
||||
Количество потоков рекомендуется определять на основании:
|
||||
|
||||
```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<Integer>
|
||||
```
|
||||
|
||||
Пример:
|
||||
|
||||
```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<Integer>
|
||||
```
|
||||
|
||||
а объединение выполняется после завершения параллельной части одним потоком.
|
||||
|
||||
---
|
||||
|
||||
# Рекомендуемые классы и импорты
|
||||
|
||||
Для реализации рекомендуется использовать стандартные средства 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<MyList<Integer>>
|
||||
↓
|
||||
Future<MyList<Integer>>
|
||||
```
|
||||
|
||||
Каждый `Callable` обрабатывает свой диапазон и возвращает собственный `MyList<Integer>`.
|
||||
|
||||
После получения всех `Future` основной метод объединяет результаты.
|
||||
|
||||
После завершения работы `ExecutorService` должен быть корректно закрыт.
|
||||
|
||||
При `InterruptedException` необходимо восстановить статус прерывания текущего потока:
|
||||
|
||||
```java
|
||||
Thread.currentThread().interrupt();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Использование `MyList`
|
||||
|
||||
Сторонние контейнеры **не допускаются**.
|
||||
|
||||
Запрещено использовать:
|
||||
|
||||
```text
|
||||
ArrayList
|
||||
LinkedList
|
||||
Vector
|
||||
CopyOnWriteArrayList
|
||||
ConcurrentLinkedQueue
|
||||
и другие стандартные контейнеры
|
||||
```
|
||||
|
||||
как хранилище промежуточных или итоговых результатов.
|
||||
|
||||
Для результатов каждого потока использовать:
|
||||
|
||||
```java
|
||||
MyList<Integer>
|
||||
```
|
||||
|
||||
Итог преобразовать в:
|
||||
|
||||
```java
|
||||
int[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Требуется ли изменять `MyList`
|
||||
|
||||
Текущего интерфейса **достаточно для реализации задачи**:
|
||||
|
||||
```java
|
||||
public interface MyList<T> {
|
||||
|
||||
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<Integer>`, существующего интерфейса достаточно.
|
||||
|
||||
### Важно
|
||||
|
||||
Метод:
|
||||
|
||||
```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<Integer>.
|
||||
9. Основной поток получает результаты через Future.
|
||||
10. Объединяет MyList<Integer> в порядке диапазонов.
|
||||
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
|
||||
```
|
||||
|
||||
изменять не требуется.
|
||||
@@ -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>`;
|
||||
- сортировки;
|
||||
- источники данных;
|
||||
- тестовые классы;
|
||||
- создание `Student`;
|
||||
- `with...()`-операции;
|
||||
- чтение и запись данных, если они используют `recordBookNumber`.
|
||||
|
||||
Например, существующий:
|
||||
|
||||
```java
|
||||
implements Comparator<Student>
|
||||
```
|
||||
|
||||
должен использовать актуальный метод:
|
||||
|
||||
```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...()`, ни внешние классы не должны позволять создать невалидный объект.
|
||||
@@ -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`.
|
||||
Reference in New Issue
Block a user