Home > Software engineering >  Correct usage of abstract case class
Correct usage of abstract case class

Time:05-20

I am trying to model a scenario where I'm trying to achieve immutability rather than modifying instance variables. The way I'm achieving this is as below.

case class TSS(k:Int, v:Int)
case class Combiner(a:Int,b:Int, tss: TSS) {
    def func1():Option[TSS] = None
    def func2():Option[TSS] = None
    
    def modifyState(externalV: Int): Combiner = {
      Combiner(this.a   1, this.b 1, func1())
    }

}


case class OneCombiner extends Combiner {
    override def func1():Option[TSS] = SOME_MODIFIED_TSS
    override def func2():Option[TSS] = SOME_MODIFIED_TSS
}

case class TwoCombiner extends Combiner {
    override def func1():Option[TSS] = SOME_MODIFIED_TSS
    override def func2():Option[TSS] = SOME_MODIFIED_TSS
}

Now when I'm trying to use the OneCombiner and TwoCombiner as below they are using the func1 and func2 of base class and setting the value of tss to None rather than using the overridden versions.

For example

val a:Combiner = OneCombiner

val anotherCombiner = a.modifyState()

In this case anotherCombiner.tss is being returned as None rather than SOME_TSS_VALUE.

What is the correct functional way of modelling such behaviour in Scala ?

I also tried making Combiner abstract case class but that failed to compile because then I was unable to create the Combiner object in modifyState method.

CodePudding user response:

The problem is that modifyState is creating a new Combiner rather than the appropriate subclass. This is because it is combining state with abstract behaviour in one class.

The best solution is to make a separate concrete State class and have the Combiners as functions that take a State and return a new one. This separates the different behaviours from the actual state itself.

case class TSS(k: Int, v: Int)
case class State(a: Int, b: Int, tss: TSS) 
trait Combiner {
  def func1(): TSS
  def modifyState(state: State, externalV: Int): State
    = State(state.a   1, state.b 1, func1())
}

case class OneCombiner() extends Combiner {
  def func1(): TSS = ???
}

case class TwoCombiner() extends Combiner {
  def func1(): TSS = ???
}

If you want to retain a single class you need to have a separate constructor for each subclass, and the easiest way to do that is to make modifyState virtual and implement it in each subclass. It is not clear how many of the other values need to be exposed in the trait so I have put them all in, just in case.

case class TSS(k: Int, v: Int)
trait Combiner {
  def a: Int
  def b: Int
  def tss: TSS
  def func1(): Option[TSS]
  def func2(): Option[TSS]
  def modifyState(externalV: Int): Combiner
}

case class OneCombiner(a: Int, b: Int, tss: TSS) extends Combiner {
  override def func1(): Option[TSS] = ???
  override def func2(): Option[TSS] = ???
  def modifyState(externalV: Int): OneCombiner =
    OneCombiner(this.a   1, this.b   1, func1().getOrElse(???))
}

case class TwoCombiner(a: Int, b: Int, tss: TSS) extends Combiner {
  override def func1(): Option[TSS] = ???
  override def func2(): Option[TSS] = ???
  def modifyState(externalV: Int): TwoCombiner =
    TwoCombiner(this.a   1, this.b   1, func1().getOrElse(???))
}
  • Related