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