Рекомендации по обновлению неизменяемых объектов в Scala

С изменяемым объектом я могу написать что-то вроде

var user = DAO.getUser(id)
user.name = "John"
user.email ="[email protected]"
// logic on user

Если пользователь неизменяемый, мне нужно клонировать \ копировать его при каждой операции изменения.

Я знаю несколько способов сделать это

  1. копия класса дела
  2. метод (например, changeName), который создает новый объект с новым свойством

Какая лучшая практика?

И еще один вопрос. Есть ли какой-либо существующий метод для получения «изменений» относительно исходного объекта (например, для создания оператора обновления)?


person sh1ng    schedule 09.07.2013    source источник
comment
Чтобы получить изменения, вы можете использовать Event Sourcing.   -  person Robin Green    schedule 09.07.2013


Ответы (1)


Оба упомянутых вами способа относятся к функциональной и объектно-ориентированной парадигмам соответственно. Если вы предпочитаете функциональную декомпозицию с абстрактным типом данных, который в Scala представлен классами case, выберите метод копирования. В моем случае использование мутаторов не является хорошей практикой, потому что это вернет вас к образу жизни Java / C # / C ++.

С другой стороны, создание класса случая ADT как

case class Person(name: String, age: String)

тогда более логично:

class Person(_name: String, _age: String) {
  var name = _name
  var age = _a

  def changeName(newName: String): Unit = { name = newName }
  // ... and so on
}

(не лучший императивный код, можно короче, но понятнее).

Конечно, есть другой способ с мутаторами - просто возвращать новый объект при каждом вызове:

class Person(val name: String, 
             val age: String) {      
  def changeName(newName: String): Unit = new Person(newName, age)
  // ... and so on
}

Но все же способ класса case более продуман.

И если вы пойдете дальше, к параллельному / параллельному программированию, вы увидите, что функциональная концепция с неизменяемым значением намного лучше, тогда пытаясь угадать, в каком состоянии сейчас находится ваш объект.

Обновить

Спасибо сению, забыл упомянуть две вещи.

Линзы
На самом базовом уровне линзы являются своего рода геттерами и сеттерами неизменяемых данных и выглядят так:

case class Lens[A,B](get: A => B, set: (A,B) => A) {
  def apply(a: A) = get(a)  
  // ...
}

Вот и все. Линза - это объект, который содержит две функции: получение и установку. get принимает A и возвращает B. set принимает A и B и возвращает новый A. Легко видеть, что тип B является значением, содержащимся в A. Когда мы передаем экземпляр для get, мы возвращаем это значение. Когда мы передаем A и B для установки, мы обновляем значение B в A и возвращаем новый A, отражающий изменение. Для удобства get имеет псевдоним для применения. Есть хорошее введение в класс корпуса Scalaz Lens.

Записи
Это, конечно, из библиотеки shapeless и называется Records. Реализация расширяемых записей, смоделированных как HLists ассоциаций. Ключи кодируются с использованием одноэлементных типов и полностью определяют типы соответствующих им значений (например, из github):

object author  extends Field[String]
object title   extends Field[String]
object price   extends Field[Double]
object inPrint extends Field[Boolean]

val book =
  (author -> "Benjamin Pierce") ::
  (title  -> "Types and Programming Languages") ::
  (price  ->  44.11) ::
  HNil

// Read price field
val currentPrice = book.get(price)  // Inferred type is Double
currentPrice == 44.11

// Update price field, relying on static type of currentPrice
val updated = book + (price -> (currentPrice+2.0))

// Add a new field
val extended = updated + (inPrint -> true)
person 4lex1v    schedule 09.07.2013
comment
Вы можете добавить к своему ответу третий способ: линзы. Для нового ответа этого недостаточно, но стоит упомянуть. - person senia; 09.07.2013