327 lines
8.2 KiB
Markdown
327 lines
8.2 KiB
Markdown
# Техническое задание: обновление `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...()`, ни внешние классы не должны позволять создать невалидный объект.
|