Загрузка данных
Вот пояснения по каждому классу: что делает и что сказать, если спросят.
Money
Один статический метод format() переводит копейки в строку «1250.50 руб.». Вынесен отдельно, чтобы не дублировать форматирование в Product, Order и ShoppingCart: в требованиях есть «не дублировать логику».
Product (товар)
• Все поля private. id сделан final, потому что по условию идентификатор не должен меняться.
• Цена хранится в long в копейках, а не в double, чтобы не было ошибок округления (0.1+0.1+0.1 ≠ 0.3).
• В конструкторе проверки: пустое имя, отрицательная цена или остаток → IllegalArgumentException.
• Склад нельзя менять напрямую, только через increaseStock и decreaseStock. Это инкапсуляция.
• decreaseStock возвращает false, если товара не хватает.
• isAvailable(q) проверяет, есть ли нужное количество.
• setPrice нужен, чтобы показать, что смена цены не влияет на старый заказ.
Customer (покупатель)
• Имя и email проверяются в сеттерах, а конструктор вызывает эти же сеттеры. Так проверка написана один раз.
• Внутри лежит List<Order> orders, потому что у покупателя может быть несколько заказов.
• getOrders() возвращает копию списка, чтобы снаружи его нельзя было испортить.
CartItem (позиция корзины)
• Хранит ссылку на Product и количество.
• getSubtotal() = цена × количество. Цена берётся из товара в данный момент.
• decreaseQuantity не даст уйти в ноль или минус: количество должно быть положительным.
ShoppingCart (корзина)
• Внутри ArrayList<CartItem>.
• addProduct: сначала проверка quantity > 0. Потом приватный метод findItem ищет, есть ли уже такой товар. Если есть, количество увеличивается, а не создаётся дубль. Склад проверяется с учётом того, что уже лежит в корзине.
• changeQuantity считает разницу между новым и текущим количеством и вызывает increase или decrease.
• removeProduct, clear, getTotal (сумма всех getSubtotal), printCart для вывода.
• Ошибки выводятся сообщением и возвращают false, программа не падает.
OrderItem (позиция заказа)
• Все поля final: это «снимок» товара на момент заказа (id, название, цена, количество).
• Ссылки на Product нет специально. Если цена в каталоге поменяется, в заказе останется старая.
OrderStatus
enum с тремя состояниями: NEW, PAID, CANCELLED. Так надёжнее, чем строки: опечатку компилятор не пропустит.
Order (заказ)
• items и totalInCents объявлены final и считаются один раз в конструкторе, поэтому после оформления их не изменить.
• В конструкторе список копируется (new ArrayList<>(items)), чтобы внешний список не влиял на заказ.
• markAsPaid() работает только из статуса NEW.
• cancel() запрещает отмену оплаченного и уже отменённого заказа.
• printOrder() выводит всю информацию.
PaymentMethod (абстрактный класс)
• abstract boolean pay(Order): у каждого способа оплаты своя реализация.
• printPaymentInfo обычный метод, общий для всех наследников, чтобы не дублировать.
• Поле name задаётся через конструктор, а наследники передают его через super(...).
CardPayment, CashPayment, OnlinePayment
Наследники PaymentMethod (отношение «является»: оплата картой является способом оплаты). Каждый переопределяет pay() с @Override, выводит своё сообщение и возвращает true. Реальной оплаты нет, по условию это имитация.
OrderService (сервис)
Здесь вся бизнес-логика, чтобы её не было в main.
• createOrder по шагам из задания: корзина не пуста → проверка склада по всем товарам → создание OrderItem → списание со склада → new Order → заказ добавляется покупателю → корзина очищается.
• Склад сначала проверяется целиком и только потом списывается. Поэтому если хоть одного товара не хватает, остатки не меняются.
• processPayment(order, paymentMethod): проверка статуса NEW, вызов pay(), при успехе markAsPaid(). Это главный пример полиморфизма: метод принимает PaymentMethod, а реально вызывается pay() карты, наличных или онлайна. Без instanceof и приведения типов.
• cancelOrder: если order.cancel() вернул true, товары возвращаются на склад. Повторная отмена вернёт false, поэтому товар дважды не вернётся.
• У сервиса есть каталог, чтобы по productId из OrderItem найти товар и вернуть его на склад.
• nextOrderId раздаёт заказам номера по порядку.
Main
Только демонстрация, логики нет.
1. Пункты 1–11 сценария по порядку: товары, покупатели, корзины, изменение и удаление, заказы, оплата картой и онлайн, отмена с возвратом на склад, итоговые остатки.
2. Цикл по List<PaymentMethod>, где каждому способу создаётся новый заказ. Это демонстрация полиморфизма.
3. Блок «Проверка некорректных ситуаций» по всем 10 пунктам из раздела 6.
4. Вспомогательные printTitle и printStock нужны только для красивого вывода.
Если спросят «где тут ООП»:
• Инкапсуляция: private поля и доступ только через методы с проверками.
• Наследование: три способа оплаты наследуются от PaymentMethod.
• Полиморфизм: processPayment работает с любым наследником через общий тип.
• Абстракция: абстрактный класс PaymentMethod.