2016-12-30 2 views
4

Я пытаюсь сделать следующий класс неизменным. Я знаю теорию о том, как это сделать, но я думаю, что моя реализация неверна. Вы можете помочь?Как сделать класс полностью неизменным в Scala

Благодаря

Mutable класс:

class BankAccount { 
private var balance = 0 
def deposit(amount: Int) { 
    if (amount > 0) 
    balance += amount 
} 
def withdraw(amount: Int): Int = 
if (0 < amount && amount <= balance) { 
    balance -= amount 
    balance   
} else { 
    error("insufficient funds") 
} 

Неизменное Класс

case class BankAccount(b:Int) { 

private def deposit(amount: Int):BankAccount { 
    if (amount > 0) 
    { 
     return BankAccount(amount) 
    } 

} 
private def withdraw(amount: Int): BankAccount ={ 
    if (0 < amount && amount <= balance) { 
     return BankAccount(b-amount)  
    } else { 
     error("insufficient funds") 
    } 
} 

}

+1

В чем ваш вопрос? У вас уже есть класс case. Используйте неявный метод 'copy' для создания новых« измененных »версий« BankAccount ». – Carcigenicate

+0

По сути, я хочу знать, успешно ли я сделал класс неизменным при сохранении его функциональности. – user3704648

+0

Вы никогда не добавляете сумму в 'deposit', вы просто заменяете существующий баланс. Кроме того, используйте «copy» вместо конструктора для создания новых экземпляров. Если вы не используете 'copy' и когда-либо добавляете новое поле в класс, вам нужно будет изменить все вызовы конструктора. Кроме этого, да, они кажутся эквивалентными. – Carcigenicate

ответ

7

В функциональном программировании вы не изменить состояние на месте, вместо того, чтобы создать новое состояние и вернуть его.

Вот как ваш вариант использования можно решить с помощью функционального программирования.

case class BankAccount(val money: Int) 

выше класс случай представляет BankAccount

Вместо того, чтобы мутировать состояние, создать новое состояние с вычисленным значением и вернуть его пользователю.

def deposit(bankAccount: BankAccount, money: Int): BankAccount = { 
    BankAccount(money + backAccount.money) 
} 

Точно так же проверьте средства и создайте новое состояние и верните его пользователю.

def withDraw(bankAccount: BankAccount, money: Int): BankAccount = { 
    if (money >= 0 && bankAccount.money >= money) { 
    BankAccount(bankAccount.money - money) 
    } else error("in sufficient funds") 
} 

В функциональном программировании очень часто создается новое государство вместо попытки изменить состояние старого состояния.

Создайте новое государство и верните, вот оно !!!

+0

Спасибо за ваш быстрый и подробный ответ! Возможно ли, что этот класс будет лучше быть изменчивым, поскольку его поля всегда будут меняться? – user3704648

12

Во-первых, хорошие новости: ваши объекты почти неизменяемые. Теперь плохие новости: они не работают.

Только «почти» неизменяемы, потому что ваш класс не является 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(_ + _) 
} 

Таким образом, отдельные операции регистрируются в журнале, и баланс рассчитывается путем сложения суммы всех сделок, которые имеют счета в качестве пункта назначения и вычитая из этого сумму суммы всех транзакций, имеющих учетную запись в качестве источника. Очевидно, что много деталей реализации я не показал вам, например. как работает журнал транзакций, и, вероятно, должно быть некоторое кэширование баланса, чтобы вам не приходилось его вычислять снова и снова. Кроме того, я проигнорировал проверку (которая также требует вычисления баланса).

Я добавил этот пример, чтобы показать вам, что одна и та же проблема может быть решена с помощью самых разных конструкций, а некоторые конструкции более естественны для функционального подхода. Обратите внимание, что эта вторая система - это способ, которым банковское дело было сделано в течение десятилетий, задолго до того, как компьютеры существовали даже, и это очень естественно относится к функциональному программированию.

Смежные вопросы