Перенесите reserve и replenish в Product, закройте quantity от прямой записи и проверяйте положительность количества. InventoryService оставляет координацию загрузки и сохранения.
Реструктуризация кода в системе онлайн‑магазина
Перенесите reserve и replenish в Product, закройте quantity от прямой записи и проверяйте положительность количества. InventoryService оставляет координацию загрузки и сохранения.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
В проекте онлайн‑магазина присутствуют несколько классов, описывающих товары и их хранилища. Реализованы две основные операции: резервирование товара и пополнение его количества. Требуется переписать код так, чтобы он был готов к дальнейшему расширению и лучше соблюдал принципы инкапсуляции.
namespace MyAwesomeShop;
final readonly class ProductId {}
final class NotEnoughProductInStock extends \Exception {}
interface ProductRepository
{
public function get(ProductId $productId): Product;
public function save(Product $product): void;
}
final class Product
{
public int $quantity = 0;
public function __construct(
public readonly ProductId $productId,
public string $title,
public string $description = '',
) {}
}
final readonly class InventoryService
{
public function __construct(
private ProductRepository $repository,
) {}
public function reserve(ProductId $itemId, int $amount): void
{
$product = $this->repository->get($itemId);
if ($product->quantity < $amount) {
throw new NotEnoughProductInStock();
}
$product->quantity -= $amount;
$this->repository->save($product);
}
public function replenish(ProductId $itemId, int $amount): void
{
$product = $this->repository->get($itemId);
$product->quantity += $amount;
$this->repository->save($product);
}
}
Ответ
Перенесите reserve и replenish в Product, закройте quantity от прямой записи и проверяйте положительность количества. InventoryService оставляет координацию загрузки и сохранения.
Смысл доменных методов:
public function reserve(int $amount): void {
if ($amount <= 0) throw new \InvalidArgumentException();
if ($this->quantity < $amount) throw new NotEnoughProductInStock();
$this->quantity -= $amount;
}
public function replenish(int $amount): void {
if ($amount <= 0) throw new \InvalidArgumentException();
if ($this->quantity > PHP_INT_MAX - $amount) throw new \OverflowException();
$this->quantity += $amount;
}
quantity становится private; начальное значение тоже валидируется. get/save должны выполняться с optimistic version check либо в транзакции с блокировкой: одна инкапсуляция не предотвращает одновременное резервирование. ProductId должен действительно хранить идентификатор.