Lazy loading some configuration options while trying to find a pattern in Scala

I want my client code to look something like this:

val config:Config = new MyConfig("c:/etc/myConfig.txt") println(config.param1) println(config.param2) println(config.param3) 

It means that:

  • Config Interface Defines Configuration Fields
  • MyConfig is a Config implementation - all the necessary wiring is the creation of the desired implementation.
  • Data is loaded lazily - this should happen when the field is first set (in this case config.param1)

So, I want the client code to be friendly, with support for interchangeable implementations, with statically typed fields hiding lazy loading. I also want it to be as simple as possible for creating alternative implementations, so Config should guide you a bit.

I am not happy with what I have come up with so far:

 trait Config { lazy val param1:String = resolveParam1 lazy val param2:String = resolveParam2 lazy val param3:Int = resolveParam3 protected def resolveParam1:String protected def resolveParam2:String protected def resolveParam3:Int } class MyConfig(fileName:String) extends Config { lazy val data:Map[String, Any] = readConfig // some dummy impl here, should read from a file protected def readConfig:Map[String,Any] = Map[String, Any]("p1" -> "abc", "p2" -> "defgh", "p3" -> 43) protected def resolveParam1:String = data.get("p1").get.asInstanceOf[String] protected def resolveParam2:String = data.get("p2").get.asInstanceOf[String] protected def resolveParam3:Int = data.get("p3").get.asInstanceOf[Int] } 

I am sure there are better solutions where you can help :)

I especially don't like the fact that MyConfig defines an intermediate container with some arbitrary keys, and since this is Map [String, Any ], I need to specify the values.

+3
source share
2 answers

There is nothing stopping you from simply abstracting the values. You cannot provide laziness in a super-tag, but this is normal, since lazy-load is indeed part of the implementation anyway:

 trait Config { val param1: String val param2: String val param3: Int } class MyConfig extends Config { lazy val param1 = readConfig().("p1") ... def readConfig(): Map[String, String] = ... } 

In a stylistic note, readConfig() should be declared and called using parens (and not without), since this is a side effect method. The no-parens syntax is for purely functional methods.

+1
source

If you just want to simplify it, the param fields can be methods ...

 trait Config { def param1:String def param2:String def param3:Int } class MyConfig(fileName:String) extends Config { lazy val data:Map[String, Any] = readConfig // some dummy impl here, should read from a file protected def readConfig:Map[String,Any] = Map[String, Any]("p1" -> "abc", "p2" -> "defgh", "p3" -> 43) def param1:String = data.get("p1").get.asInstanceOf[String] def param2:String = data.get("p2").get.asInstanceOf[String] def param3:Int = data.get("p3").get.asInstanceOf[Int] } 

To get rid of casting, you could wrap MyConfig non-lazy inner class, which is lazy loaded by MyConfig .

 class MyConfig(fileName:String) extends Config { private class NonLazyConfig(val p1:String, p2:String, p3:int) extends Config { def param1 = p1 def param2 = p2 def param1 = p3 } lazy val inner:Config = readConfig // some dummy impl here, should read from a file protected def readConfig:Config = { return new NonLazyConfig("abc", "defgh", 43) } def param1:String = inner.param1 def param2:String = inner.param2 def param3:Int = inner.param3 } 
0
source

All Articles