Implicits
The most obvious solution for injecting dependencies is to add parameters to object’s constructor (you can also do it by defining properties, but in that case your object becomes mutable). Additionally Scala provides you implicit parameters, which can be automatically supplied.
class Calculator (implicit val dataProvider:DataProvider, implicit val operation:Operation) { def calculate(): Int = operation.exec(dataProvider.nextArgument, dataProvider.nextArgument) } val calc = new Calculator
Here Calculator’s constructor has two arguments marked as implicit. This means that they can be omitted if default objects of the same type are defined. Above implementation will generate compilation error, because these defaults are missing. To provide them we can for example define and import configuration object.
object AdditionFromFakeDbDataConfig { implicit val operation: Operation = new Addition implicit val dataProvider: DataProvider = new FakeDbDataProvider }
import AdditionFromFakeDbDataConfig._ val calc = new Calculator
Structural typing
This is Scala feature to provide duck typing in a type-safe manner. Duck typing is a style of typing in which an object's methods and properties determine the valid semantics, rather than its inheritance from a particular class or implementation of a specific interface. In Scala you can achieve that by replacing data type with code block containing all required methods and properties.
Calculator(dataProvider: { val x: Int; val y: Int}, operation: { def exec(a: Int, b: Int): Int}) { ... }Calculator class requires two arguments to be passed to its constructor. First needs to define two values x and y, and the second one must contains function exec taking two integers and returns integer. For example you can pass two objects:
object Data { val x = 5 val y = 3 } object Avg { def exec(a: Int, b: Int): Int = (a + b) / 2 }
This solution gives us opportunity to aggregate all required resources into one object and pass that object into constructor. It is good to remember that Scala's structural typing uses reflection, so one can face some performance problems when he is using this solution vastly. Additional information about Structural Typing can be found on this site.
Cake Pattern
Cake Pattern is the last method that I want to mention about in this post. Cake Pattern combines few Scala features:
- Trait with abstract value First of all you need to create a namespace using trait, containing all classes that you need to inject e.g.
- Self type Self type can be used to access fields or functions of other type which implementation will be mixed in later e.g.
- Mixins Last step is to combine all components in one object.
Addition
and Multiplication
are containted in Operations
namespace, additionally there is abstract value defined that need to be instantiate in non-abstract object. trait Operations { val operation: Operation trait Operation { def exec(a: Int, b: Int): Int } class Addition extends Operation { def exec(a: Int, b: Int): Int = a + b } class Multiplication extends Operation { def exec(a: Int, b: Int): Int = a * b } }
CalculatorComponent
requires two traits DataProviders
and Operations
, both of them are in fact namespaces with abstract values: dataProvider
and operation
, that is why they can be accessed in Calculator
class trait CalculatorComponent { this: DataProviders with Operations => val calculator = new Calculator class Calculator { def calculate(): Int = operation.exec(dataProvider.nextArgument, dataProvider.nextArgument) } }
object AdditionFromFakeDbDataConfig extends CalculatorComponent with Operations with DataProviders { val operation = new Addition val dataProvider = new FakeDbDataProvider }
- DI library Subcut and video with presentation
- Martin Odersky Scalable Component paper
- nicely organised Eran Medan's article.
No comments:
Post a Comment