0

I have a simple trait

trait SomeTrait {
  val sourceData: SourceData
}

SourceData class has constructor parameter p: Array[String]. Now, when I extend this trait in Object, we must provide implementation for sourceData.

object SomeObject extends SomeTrait {
  override val sourceData: SourceData = ???
  def main(sysArgs: Array[String]){...}
}

But what if class SourceData needs sysArgs from main method, how can I override sourceData in main method, not in body of SomeObject. Something like this:

object SomeObject extends SomeTrait {
  def main(sysArgs: Array[String]){
    override val sourceData: SourceData = new SourceData(sysArgs)
  }
}

I do not want to use var, as val immutability is preferred. And also I want to have trait with no implementation in order to force all sub classes to implement sourceData. What other solution I have for this?

7
  • 1
    Another thing you can do, is having sourceData as an Option, and when the method main is called, create a new SomeObject with the relevant SourceData that you need, and use it. According to the terminology you used, I assume this is part of your main class. In this case you can't do that. But think about what you can extract into another class and do it there. Commented Dec 2, 2020 at 13:36
  • @TomerShetah SomeObject is a top-level object so you can't create a new one once the program is running. Commented Dec 2, 2020 at 15:42
  • @Tim, your comment is already addressed in mine. It is not clear from the question that it has to be a top level object. If it can be extracted to another object this approach might work. Commented Dec 2, 2020 at 15:47
  • @TomerShetah I think the fact that it is shown as a top-level object in the code, and is called SomeObject, and it contains main so it must exist before main is called, and the word "object" is in the question, makes it pretty clear that it is supposed to be a top-level object. Commented Dec 2, 2020 at 15:59
  • @TomerShetah And if you do create it dynamically so that it does not need to be mutable, making the value an Option does not help. Commented Dec 2, 2020 at 16:02

2 Answers 2

2

You can't avoid mutability in this situation. sourceData must have a value before main is called, and main must be able to change that value, so the value must be mutable.

One option is to make sourceData a def (which is a good idea anyway) and have it access a private var in SomeObject:

trait SomeTrait {
  def sourceData: SourceData
}

object SomeObject extends SomeTrait {
  private var mySource: SourceData = ???
  def sourceData = mySource

  def main(sysArgs: Array[String]) = {
    mySource = new SourceData(sysArgs)
  }
}

The root problem here is having a top-level object that needs run-time initialisation. This is required because SomeObject is a top-level object that is accessed directly from other parts of the code.

The solution to this is dependency injection rather than a global object.

trait SomeTrait {
  def sourceData: SourceData
}

object SomeObject {
  case class SomeData(sourceData: SourceData) extends SomeTrait

  def main(sysArgs: Array[String]) = {
    val theSource = SomeData(SourceData(sysArgs))

    // Pass this instance to the rest of the code that needs it
    restOfTheProgram(theSource)
  }
}

The rest of the code uses the instance of SomeTrait that is passed to it rather than using SomeObject directly.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for your comments. Code I've provided is just simplified snippet from a much more complex. I've just made it simple to emphasize the problem I'm trying to solve. But I forgot to mention that in trait there are few additional abstract methods that needs override in SomeObject.
To explain what I'm doing here. We have a lot of Spark jobs. Each one needs many S3 locations and other configurations. This locations depend on sysArgs, so idea is to generate class(base on sysArgs) with properties, and then use property in job like this for instance mySource.inputS3Location. This is going to be different path depending on a job and basically on sysArgs.
@datahack The solution is to create a class based on sysArgs and pass it to the functions that need it rather than putting it in a global variable. This is called Dependency Injection and is one of the SOLID principles of software design.
I'm definitely missing something. How can I in this example, force variable override and use DI?
1

There is no way to do that in Scala. You need to have a class inherit SomeTrait and instantiate it from the main method.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.