32. Реализовать перевод средств между аккаунтами
Есть сервис перевода денег между аккаунтами. Необходимо реализовать метод transferAmount(Account from, Account to, BigDecimal amount), который переводит сумму с одного аккаунта на другой.
Дополнительно нужно обосновать выбор типа для суммы перевода.
Требования к решению:
все данные хранятся в памяти, без БД
сетевого взаимодействия нет
важно учесть валидацию входных данных, бизнес-логику, корректность и производительность
при выборе типа для суммы перевода нужно избежать потери точности
Код:
class Account {
// TODO Сюда можно добавлять поля
}
class TransferService {
void transferAmount(Account from, Account to, ??? amount) {
// TODO Сюда добавляем логику списания/пополнения счетов
}
}
Спойлеры к решению
Подсказки
💡 Тип
💡 Проверьте, что
💡 Убедитесь, что
💡 Перед списанием проверяйте, что
💡 Для потокобезопасности: блокируйте аккаунты в порядке
BigDecimal — подходит для денег: точные вычисления без потерь.💡 Проверьте, что
from, to и amount не null, и amount > 0.💡 Убедитесь, что
from и to — разные аккаунты.💡 Перед списанием проверяйте, что
from.getBalance().compareTo(amount) >= 0.💡 Для потокобезопасности: блокируйте аккаунты в порядке
id, чтобы избежать дедлока.Решение
import java.math.BigDecimal;
import java.util.Objects;
public class TransferService {
public void transferAmount(Account from, Account to, BigDecimal amount) {
// Валидация входных параметров
Objects.requireNonNull(from, "Source account must not be null");
Objects.requireNonNull(to, "Target account must not be null");
Objects.requireNonNull(amount, "Amount must not be null");
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Amount must be positive");
}
if (from.getId().equals(to.getId())) {
throw new IllegalArgumentException("Cannot transfer to the same account");
}
// Выбираем порядок блокировок по id для избежания дедлоков
Account first = from.getId().compareTo(to.getId()) < 0 ? from : to;
Account second = first == from ? to : from;
synchronized (first) {
synchronized (second) {
// Проверка достаточности средств
if (from.getBalance().compareTo(amount) < 0) {
throw new IllegalArgumentException(
"Insufficient funds in account " + from.getId());
}
// Выполняем перевод
from.debit(amount);
to.credit(amount);
}
}
}
}