Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Design Patterns

Design patterns are common solutions to recurring problems in software design. In Scala, many design patterns can be implemented concisely and elegantly due to the language's rich features such as case classes, traits, and functional programming constructs. Here are examples of some common design patterns in Scala:

1. Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it.

object Singleton:
  def doSomething(): Unit =
    println("Doing something...")

// Usage
Singleton.doSomething()

2. Factory Pattern

The Factory pattern provides an interface for creating objects without specifying the exact class of the object that will be created.

sealed trait Animal:
  def makeSound(): String

case class Dog() extends Animal:
  def makeSound(): String = "Woof"

case class Cat() extends Animal:
  def makeSound(): String = "Meow"

object AnimalFactory:
  def createAnimal(animalType: String): Animal =
    animalType.toLowerCase match
      case "dog" => Dog()
      case "cat" => Cat()
      case _ => throw new IllegalArgumentException("Unknown animal type")

// Usage
val dog = AnimalFactory.createAnimal("dog")
println(dog.makeSound()) // Woof

val cat = AnimalFactory.createAnimal("cat")
println(cat.makeSound()) // Meow

3. Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The Strategy pattern lets the algorithm vary independently from the clients that use it.

trait PaymentStrategy:
  def pay(amount: Double): String

class CreditCardPayment extends PaymentStrategy:
  def pay(amount: Double): String = s"Paid $$amount using credit card."

class PayPalPayment extends PaymentStrategy:
  def pay(amount: Double): String = s"Paid $$amount using PayPal."

class PaymentContext(strategy: PaymentStrategy):
  def executeStrategy(amount: Double): String = strategy.pay(amount)

// Usage
val creditCardPayment = new PaymentContext(new CreditCardPayment)
println(creditCardPayment.executeStrategy(100)) // Paid $100.0 using credit card.

val payPalPayment = new PaymentContext(new PayPalPayment)
println(payPalPayment.executeStrategy(200)) // Paid $200.0 using PayPal.

4. Observer Pattern

The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

trait Observer:
  def update(message: String): Unit

class ConcreteObserver(name: String) extends Observer:
  def update(message: String): Unit =
    println(s"$name received message: $message")

trait Subject:
  private var observers: List[Observer] = List()

  def addObserver(observer: Observer): Unit =
    observers = observer :: observers

  def removeObserver(observer: Observer): Unit =
    observers = observers.filterNot(_ == observer)

  def notifyObservers(message: String): Unit =
    observers.foreach(_.update(message))

class ConcreteSubject extends Subject:
  def changeState(message: String): Unit =
    println(s"Subject state changed: $message")
    notifyObservers(message)

// Usage
val subject = new ConcreteSubject

val observer1 = new ConcreteObserver("Observer1")
val observer2 = new ConcreteObserver("Observer2")

subject.addObserver(observer1)
subject.addObserver(observer2)

subject.changeState("New state") // Both observers receive the message

5. Decorator Pattern

The Decorator pattern attaches additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

trait Coffee:
  def cost: Double
  def description: String

class SimpleCoffee extends Coffee:
  def cost: Double = 2.0
  def description: String = "Simple coffee"

class MilkDecorator(coffee: Coffee) extends Coffee:
  def cost: Double = coffee.cost + 0.5
  def description: String = coffee.description + ", milk"

class SugarDecorator(coffee: Coffee) extends Coffee:
  def cost: Double = coffee.cost + 0.2
  def description: String = coffee.description + ", sugar"

// Usage
val coffee = new SimpleCoffee
println(s"${coffee.description} costs ${coffee.cost}") // Simple coffee costs 2.0

val milkCoffee = new MilkDecorator(coffee)
println(s"${milkCoffee.description} costs ${milkCoffee.cost}") // Simple coffee, milk costs 2.5

val milkSugarCoffee = new SugarDecorator(milkCoffee)
println(s"${milkSugarCoffee.description} costs ${milkSugarCoffee.cost}") // Simple coffee, milk, sugar costs 2.7

6. Command Pattern

The Command pattern encapsulates a request as an object, thereby allowing for parameterization of clients with different requests, queuing of requests, and logging of requests.

trait Command:
  def execute(): Unit

class Light:
  def on(): Unit = println("The light is on")
  def off(): Unit = println("The light is off")

class LightOnCommand(light: Light) extends Command:
  def execute(): Unit = light.on()

class LightOffCommand(light: Light) extends Command:
  def execute(): Unit = light.off()

class RemoteControl:
  private var command: Option[Command] = None

  def setCommand(command: Command): Unit =
    this.command = Some(command)

  def pressButton(): Unit =
    command.foreach(_.execute())

// Usage
val light = new Light
val lightOn = new LightOnCommand(light)
val lightOff = new LightOffCommand(light)

val remote = new RemoteControl
remote.setCommand(lightOn)
remote.pressButton() // The light is on

remote.setCommand(lightOff)
remote.pressButton() // The light is off