Ответ 1
Вы можете эффективно использовать неизменяемые объекты в Scala и избегать ужаса изменяемых полей и всех ошибок, которые происходят из изменяемого состояния. Использование Неизменяемых сущностей помогает вам с concurrency, не ухудшает ситуацию. Ваше предыдущее изменчивое состояние станет набором преобразований, которое создаст новую ссылку при каждом изменении.
На определенном уровне вашего приложения, однако, вам нужно будет иметь изменяемое состояние, или ваше приложение будет бесполезным. Идея состоит в том, чтобы подталкивать ее так, как вы можете в своей программной логике. Давайте рассмотрим пример банковского счета, который может измениться из-за процентной ставки и снятия банкомата или депозит.
У вас есть два действительных подхода:
-
Вы публикуете методы, которые могут изменять внутреннее свойство, и вы управляете concurrency этими методами (на самом деле очень мало)
-
Вы делаете весь класс неизменным и окружаете его с помощью "менеджера", который может изменить учетную запись.
Так как первый довольно простой, я подробно расскажу о первом.
case class BankAccount(val balance:Double, val code:Int)
class BankAccountRef(private var bankAccount:BankAccount){
def withdraw(withdrawal) = {
bankAccount = bankAccount.copy(balance = bankAccount.balance - withdrawal)
bankAccount.balance
}
}
Это хорошо, но, черт возьми, вы все еще придерживаетесь управления concurrency. Ну, Scala предлагает вам решение для этого. Проблема здесь в том, что если вы поделитесь своей ссылкой на BankAccountRef на фоновое задание, вам придется синхронизировать вызов. Проблема в том, что вы делаете concurrency неоптимальным способом.
Оптимальный способ выполнения concurrency: передача сообщений
Что делать, если с другой стороны разные задания не могут вызывать методы непосредственно на BankAccount или BankAccountRef, а просто уведомлять их о необходимости выполнения некоторых операций? Ну, тогда у вас есть Актер, любимый способ сделать concurrency в Scala.
class BankAccountActor(private var bankAccount:BankAccount) extends Actor {
def receive {
case BalanceRequest => sender ! Balance(bankAccount.balance)
case Withdraw(amount) => {
this.bankAccount = bankAccount.copy(balance = bankAccount.balance - amount)
}
case Deposit(amount) => {
this.bankAccount = bankAccount.copy(balance = bankAccount.balance + amount)
}
}
}
Это решение подробно описано в документации Akka: http://doc.akka.io/docs/akka/2.1.0/scala/actors.html. Идея заключается в том, что вы общаетесь с Актером, отправляя сообщения в свой почтовый ящик, и эти сообщения обрабатываются в порядке получения. Таким образом, у вас никогда не будет недостатков concurrency, если вы используете эту модель.