Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

scala dynamically determined type

Tags:

scala

I want to create object instance whose type is determined by runtime data:

trait Business
case class Business1() extends Business 
case class Business2() extends Business

object Business {
  def fromData(data:Array[Byte]): Business = data(0) match {
    case 1 => new Business1
    case 2 => new Business2
    case _ => throw new RuntimeException("data error")
  }
}

The above code can do its job but has a problem that it is closed. Whenever I implement a new Business subclass, I'll have to modify Business.fromData code, e.g.

case 3 => new Business3

How can I define Business.fromData once and can later add Business3, Business4 without registering to it?

Edit

I finally realized that this is a perfect use case of Multimethod, that is, dispatching based on a function of some argument. So the more general question should be "How to do multimethod in scala"? I believe design patterns exist only because of language incapability, that is why I am reluctant to accept a factory based answer.

like image 452
xiefei Avatar asked Dec 02 '12 04:12

xiefei


3 Answers

This doesn't solve your problem but if you make Business a "sealed" trait then the compiler will at catch any non-exhaustive match until you've updated fromData:

sealed trait Business
case class Business1() extends Business
case class Business2() extends Business

biz match {
    case Business1 => println("Business1")
}

...will result in...

warning: match is not exhaustive!
missing combination            Business2
like image 81
Ryan Avatar answered Nov 13 '22 23:11

Ryan


You could also do this. Although I'm not sure if that is better then match case. Depends on what you are trying to do.

class Business {
  override def toString = "Business"
}

val factories: Map[Int, () => Business] = Map(
  1 -> (() => new Business {
    override def toString = "Business1"
  }),
  2 -> (() => new Business {
    override def toString = "Business2"
  }),
  3 -> (() => new Business {
    override def toString = "Business3"
  })
)

object Business {
  def fromData(data: Array[Byte]): Business = factories(data(0))()
}

val b = Business.fromData(Array(1,1,2))
println(b)
like image 41
SpiderPig Avatar answered Nov 13 '22 22:11

SpiderPig


The classic answer is by using a factory with registration, a.k.a. abstract factory.

So given your hierarchy above, you'd create a 'factories' map just like the one presented here in another answer, but you'd also create a parallel hierarchy of object creators, and register them on start up, like so:

trait BusinessCreator {
  def createBusiness() : Business 
}
object BusinessCreator1() extends BusinessCreator {
  override def createBusiness() : Business = new Business1()

  factories += "1" -> this
}
//etc.

Another more 'Scalaish' way to do it would be to skip the parallel hierarchy and just register a creator function in the factories object from a companion object to each class, but the idea is the same.

like image 36
Or Peles Avatar answered Nov 13 '22 22:11

Or Peles