Во-первых, хорошие новости: ваши объекты почти неизменяемые. Теперь плохие новости: они не работают.
Только «почти» неизменяемы, потому что ваш класс не является final
: Я могу расширить его и переопределить методы для изменения состояния.
Теперь, почему это не работает? Наиболее очевидная ошибка заключается в том, что в вашем методе deposit
вы возвращаете новый BankAccount
, у которого его баланс установлен на сумму, которая была депонирована. Таким образом, вы теряете все деньги, которые были там перед депозитом! Вам необходимо добавить депозит на баланс, не заменить остаток с депозитом.
Есть и другие проблемы: Ваш метод deposit
имеет тип возвращаемого BankAccount
, но это не всегда возвращают BankAccount
: если amount
меньше или равна нулю, то она возвращает Unit
. Наиболее специфическим общим супертипом BankAccount
и Unit
является Any
, поэтому ваш метод фактически возвращает Any
. Существует несколько способов исправить это, например. возвращая Option[BankAccount]
, Try[BankAccount]
или Either[SomeErrorType, BankAccount]
, или просто бросая исключение. Для моего примера я просто полностью игнорирую проверку. (Аналогичная проблема существует в withdraw
.)
Что-то вроде этого:
final case class BankAccount(balance: Int) {
private def deposit(amount: Int) = copy(balance = balance + amount)
private def withdraw(amount: Int) = copy(balance = balance - amount)
}
Примечание Я использую компилятор сгенерированный copy
метод конкретных классов, который позволяет создать копию экземпляра только один поле изменилось. В вашем конкретном случае у вас есть только одно поле, но это хорошая практика.
Итак, это работает. Или ... не так ли? Ну, нет, на самом деле, нет! Проблема в том, что мы создаем новые банковские счета ... с деньгами в них ... мы создаем новые деньги из воздуха! Если у меня есть 100 долларов на моем счете, я могу снять 90 из них, и я получаю новый объект банковского счета с 10 долларами в нем. Но у меня все еще есть доступ к старому объекту банковского счета с 100 долларами в нем! Итак, у меня есть два банковских счета, в общей сложности 110 долларов плюс 90 я ушел; У меня теперь есть 200 долларов!
Решение это нетривиально, и я оставлю его пока.
В заключение я хотел показать вам кое-что, что немного близко к тому, как реально функционируют реальные банковские системы , с помощью которых я имею в виду «банковские системы в реальном мире, как и раньше» изобретение электронных банковских услуг », а также« электронные банковские системы, поскольку они фактически используются », потому что на удивление (или нет) они фактически работают одинаково.
В вашей системе, баланс данные и пополнения и снятия являются операции. Но в реальном мире это именно двойное: депозиты и снятия составляют данных, а вычисление баланса - это операция . Перед тем, как мы запустим компьютеры, банковские счетчики будут писать транзакционные промахи для каждой транзакции, тогда эти сальдо транзакций будут собраны в конце дня, и все денежные движения будут добавлены. И электронные банковские системы работают так же, примерно так:
final case class TransactionSlip(source: BankAccount, destination: BankAccount, amount: BigDecimal)
final case class BankAccount {
def balance =
TransactionLog.filter(slip.destination == this).map(_.amount).reduce(_ + _) -
TransactionLog.filter(slip.source == this).map(_.amount).reduce(_ + _)
}
Таким образом, отдельные операции регистрируются в журнале, и баланс рассчитывается путем сложения суммы всех сделок, которые имеют счета в качестве пункта назначения и вычитая из этого сумму суммы всех транзакций, имеющих учетную запись в качестве источника. Очевидно, что много деталей реализации я не показал вам, например. как работает журнал транзакций, и, вероятно, должно быть некоторое кэширование баланса, чтобы вам не приходилось его вычислять снова и снова. Кроме того, я проигнорировал проверку (которая также требует вычисления баланса).
Я добавил этот пример, чтобы показать вам, что одна и та же проблема может быть решена с помощью самых разных конструкций, а некоторые конструкции более естественны для функционального подхода. Обратите внимание, что эта вторая система - это способ, которым банковское дело было сделано в течение десятилетий, задолго до того, как компьютеры существовали даже, и это очень естественно относится к функциональному программированию.
В чем ваш вопрос? У вас уже есть класс case. Используйте неявный метод 'copy' для создания новых« измененных »версий« BankAccount ». – Carcigenicate
По сути, я хочу знать, успешно ли я сделал класс неизменным при сохранении его функциональности. – user3704648
Вы никогда не добавляете сумму в 'deposit', вы просто заменяете существующий баланс. Кроме того, используйте «copy» вместо конструктора для создания новых экземпляров. Если вы не используете 'copy' и когда-либо добавляете новое поле в класс, вам нужно будет изменить все вызовы конструктора. Кроме этого, да, они кажутся эквивалентными. – Carcigenicate