Scala Design Patterns
Create a new project from template¶
Use the “sbt new” command, providing the name of the template. For example, “$ sbt new akka/hello-akka.g8”. You can find a list of templates here.
Or download from Scala Project Templates
Static Factory¶
trait Animal
class Bird extends Animal
class Mammal extends Animal
class Fish extends Animal
object Animal {
def apply(animal: String): Animal = animal.toLowerCase match {
case "bird" => new Bird
case "mammal" => new Mammal
case "fish" => new Fish
case x: String => throw new RuntimeException(s"Unknown animal: $x")
}
}
Algebraic Data Types and Pattern Matching¶
- Goal: translate data descriptions into code
- Model data with logical ors and logical ands
- Two patterns: product types (and) sum types (or)
- Product type: A has a B and C
- Sum type: A is a B or C
- Sum and product together make algebraic data types
// A has a B and C
case class A(b: B, c: C)
// A is a B or C
sealed trait A
case class B() extends A
case class C() extends A
They have only data and do not contain any functionality on top of this data as normal classes would.
sealed trait Shape
case class Circle(radius: Double) extends Shape
case class Rectangle(height: Double, width: Double) extends Shape
object Shape {
def area(shape: Shape): Double =
shape match {
case Circle(Point(x, y), radius) => Math.PI * Math.pow(radius, 2) // use pattern matching to process
case Rectangle(_, h, w) => h * w
}
}
Stackable Traits¶
abstract class StringWriter {
def write(data: String): String
}
class BasicStringWriter extends StringWriter {
override def write(data: String): String =
s"Writing the following data: ${data}"
}
trait CapitalizingStringWriter extends StringWriter {
abstract override def write(data: String): String = {
super.write(data.split("\\s+").map(_.capitalize).mkString(" "))
}
}
trait UppercasingStringWriter extends StringWriter {
abstract override def write(data: String): String = {
super.write(data.toUpperCase)
}
}
object Example {
def main(args: Array[String]): Unit = {
val writer1 = new BasicStringWriter with UppercasingStringWriter with CapitalizingStringWriter
System.out.println(s"Writer 1: '${writer1.write("we like learning scala!")}'")
}
}
Stackable traits order of execution¶
Stackable traits are always executed from the right mixin to the left. Sometimes, however, if we only get output and it doesn't depend on what is passed to the method, we simply end up with method calls on a stack, which then get evaluated and it will appear as if things are applied from left to right.
Components / Cake Pattern¶
https://jonasboner.com/real-world-scala-dependency-injection-di/
// Service Interfaces and Component Definitions
trait OnOffDeviceComponent {
val onOff: OnOffDevice // abstract val
trait OnOffDevice {
def on: Unit
def off: Unit
}
}
trait SensorDeviceComponent {
val sensor: SensorDevice
trait SensorDevice {
def isCoffeePresent: Boolean
}
}
// =======================
// Component / Service Implementations
trait OnOffDeviceComponentImpl extends OnOffDeviceComponent {
class Heater extends OnOffDevice {
def on = println("heater.on")
def off = println("heater.off")
}
}
trait SensorDeviceComponentImpl extends SensorDeviceComponent {
class PotSensor extends SensorDevice {
def isCoffeePresent = true
}
}
// =======================
// Component declaring two dependencies that it wants injected
trait WarmerComponentImpl {
this: SensorDeviceComponent with OnOffDeviceComponent => // Use of self-type for composition
class Warmer {
def trigger = {
if (sensor.isCoffeePresent) onOff.on
else onOff.off
}
}
}
// =======================
// Instantiation (and configuration) of the services in the ComponentRegistry module
object ComponentRegistry extends
OnOffDeviceComponentImpl with
SensorDeviceComponentImpl with
WarmerComponentImpl {
val onOff = new Heater // all instantiations in one spot; can be easily be replaced by e.g. mocks
val sensor = new PotSensor
val warmer = new Warmer
}
// =======================
val warmer = ComponentRegistry.warmer
warmer.trigger
Type Classes (using context-bound type parameters)¶
- Ad-hoc polymorphism
- Break free from your class oppressors!
- Concerns that cross class hierarchy e.g. serialize to JSON
- Common behaviour without (useful) common type
- Abstract behaviour to a type class
- Can implement type class instances in ad-hoc manner
// Define some behavior in terms of operations that a type must support in order to be considered a member of the type class.
trait Number[T] {
def plus(x: T, y: T): T
def divide(x: T, y: Int): T
}
// Define the default type class members in the companion object of the trait
object Number {
implicit object DoubleNumber extends Number[Double] { // note the implicit
override def plus(x: Double, y: Double): Double = x + y
override def divide(x: Double, y: Int): Double = x / y
}
}
object Stats {
// older pattern with implicit parameter
// def mean[T](xs: Vector[T])(implicit ev: Number[T]): T = // note the implicit
// ev.divide(xs.reduce(ev.plus(_, _)), xs.size)
def mean[T: Number](xs: Vector[T]): T = // note the context bound
implicitly[Number[T]].divide(
xs.reduce(implicitly[Number[T]].plus(_, _)), // retrieve the evidence via implicitly[]
xs.size
)
}
Visitor Pattern¶
abstract class Element(text: String) {
def accept(visitor: Visitor)
}
case class Title(text: String) extends Element(text) {
override def accept(visitor: Visitor): Unit = {
visitor.visit(this)
}
}
case class Text(text: String) extends Element(text) {
override def accept(visitor: Visitor): Unit = {
visitor.visit(this)
}
}
class Document(parts: List[Element]) {
def accept(visitor: Visitor): Unit = {
parts.foreach(p => p.accept(visitor))
}
}
trait Visitor {
def visit(element: Element)
}
class VisitorImpl1 extends Visitor {
override def visit(element: Element): Unit = {
element match {
case Title(text) => ???
case Text(text) => ???
//...
}
}
}
Configuration¶
import com.typesafe.config.ConfigFactory
trait AppConfigComponent {
val appConfigService: AppConfigService
class AppConfigService() {
//-Dconfig.resource=production.conf for overriding
private val conf = ConfigFactory.load()
private val appConf = conf.getConfig("job-scheduler")
private val db = appConf.getConfig("db")
val configPath = appConf.getString("config-path")
val configExtension = appConf.getString("config-extension")
val workers = appConf.getInt("workers")
val dbConnectionString = db.getString("connection-string")
val dbUsername = db.getString("username")
val dbPassword = db.getString("password")
}
}
Memoization¶
import scala.collection.mutable.Map
trait Memoizer {
def memo[X, Y](f: X => Y): (X => Y) = {
val cache = Map[X, Y]()
(x: X) => cache.getOrElseUpdate(x, f(x))
}
}
Using scalaz:
Pimp my Library Pattern¶
The pimp my library design pattern is really similar to extension methods in C#.