0

In my code base, I want to refractor away from vars. The code base structure follows the format:

class Animal {
    var name : Option[String] = None
    var owner : Option[String] = None
}

case class Dog(breed: String) extends Animal {
    //Dog logic
}

The main reason for this design is that information is not available all at the same time.

So, from service A, which deserializes a json, I receive an Animal (either Animal, Dog)

val animal = new Animal 
animal.name = "My little animal"

and then

def update(animal: Animal) : Unit {
    animal match {
        case a : Animal => a.owner = Some("Joe") // call to service B
        case _          => //handle dog
    }
}

update(animal)

The question is: How can I redesign it to avoid mutable state in Animal?

  • Write a copy method in Animal? There are a bunch of fields, which might generate some boilerplate

  • Composition?

Making the two classes case?

case class Animal (...)
case class Dog(animal: Animal, breed: String)

Edit

  • Animal as a trait

I still need a concrete implementation of Animal, as I will have Dogs and Animals (which are really Animal and no subtype)

  • Problems with copy

The built in case class copy method, does NOT update the values of the Animal class. So defaults values will be used - not what I want. - yes, it is possible to create a case class with all the fields, but is it practical if we have 100+ fields?

Bruno
  • 252
  • 1
  • 8
  • 1
    Case classes include a copy method https://stackoverflow.com/questions/7249396/how-to-clone-a-case-class-instance-and-change-just-one-field-in-scala – ameer Dec 21 '17 at 18:40

2 Answers2

0

You could design this as an ADT (Abstract Data Type) like this:

trait Animal {
  def name: Option[String]
  def owner: Option[String]
}

case class Dog(dog: Option[String] = None, owner: Option[String] = None) extends Animal

With a case class you have the copy method by default!

joesan
  • 13,963
  • 27
  • 95
  • 232
0

A good solution could be to make a trait which specifies Animal behavior,

sealed trait Animal {
  val name: Option[String]
  val owner: Option[String]
}

and then make case classes to provide type constructors for your animal instances.

case class Dog(name: Option[String], owner: Option[String]) extends Animal

Now, update (changeOwner) will take an Animal and return another Animal likewise:

def changeOwner(animal: Animal): Animal = animal match {
  case Dog(name, owner) => Dog(name, Some("Joe"))
  case _  => animal
}

And you will use it as follows:

val dog: Animal = Dog(Some("Doggie"), Some("Jack"))
val newDog: Animal = changeOwner(dog)

Also, from your code:

case a : Animal => a.owner = Some("Joe") // call to service B

Be careful with this, as the first case of your match will take all animals and thus the other cases, for example, your Dog case, will be unreachable.

nicodp
  • 2,362
  • 1
  • 11
  • 20