WestCoastProjects
WestCoastProjects

Reputation: 63062

Preferred manner to declare and initialize instance/class variables

In the following snippet from either a class or object (I presume the treatment of the two would be similar),:

  private var consumer : Consumer = _
  def getConsumer(channel : Channel)  = if (consumer != null) {
    consumer } 
  else {
    // build it
  }

I do not believe that we would blindly just throw an Option around it in all cases:

  private var consumer : Option[Consumer] = None
  def getConsumer(channel : Channel)  = consumer.getOrElse(
            // build it
            consumer = Some(/* built val */)
  }

Yes the above is quite possible, but it is my hunch there are alternatives out there. Insights appreciated.

EDIT The consumer object is being sent directly to third party api; therefore no changes/embellishments to it that require signature changes are possible here.

Here is an example:

channel.basicConsume(queue, true, getConsumer(channel))

OR

// assuming consumer were already constructed with a Channel instance in the constructor
channel.basicConsume(queue, true, consumer)

Upvotes: 2

Views: 127

Answers (2)

Nikita Volkov
Nikita Volkov

Reputation: 43309

The pattern you're trying to recreate is called Memoization.

val getConsumer = {
  val cache = collection.mutable.Map.empty[Channel, Consumer]
  (channel : Channel) => cache.getOrElseUpdate(channel, Consumer(channel))
}

It's worth mentioning that the above goes with the presumption that you build Consumer in its constructor. Otherwise you can use whatever function you want instead of Consumer(channel).

This pattern is wrapped by a lot of Scala libraries, so you can just depend on one. E.g., Scalaz.

Upvotes: 1

ggovan
ggovan

Reputation: 1927

Ideally you want to initialise all your fields when an object is constructed. lazy vals give the option of holding off on that initialisation until the value is needed to be used. In your use case I would recommend passing the channel into the constructor and using it as below:

case class WithChannel(channel: Channel){
  lazy val consumer = {create consumer, you can use channel here}

  channel.basicConsume(queue, true, consumer)
}

If it is not possible to always have channel when you construct the rest of the object then it might be useful to have a class to represent the uninitialised case.

case class Uninitialised(someArg: Any){
  def withChannel(channel: Channel) = Initialised(someArg, channel)
}

case class Initialised(someArg: Any, channel: Channel){
  lazy val consumer = { some means of creating the consumer }

  channel.basicConsume(queue, true, consumer)
}

val uninit = Uninitialised("Bob")
val init = uninit.withChannel(channel)

The benefit of doing it this way is that there are nulls, no Options, the state of the object is described by its type, not its memebers.


Were you to use a class instead of a case class

class Initialised(someArg:Any, val channel: Channel) 

Note the val before channel. This makes channel a field of the class instead of just an argument to the constructor. This is necessary if you want to use it outside of the initialisation of the object, e.g. in methods and to construct lazy vals. Parameters to case classes are implicitly val.

Upvotes: 3

Related Questions