Lesson 3
Building a Flexible Smart Home System with Command and Decorator Patterns in Scala
Introduction

Welcome to the third lesson of our "Applying Design Patterns for Real World Problems using Scala" course! In this lesson, we'll delve into two powerful design patterns: the Command pattern and the Decorator pattern. These patterns will empower you to design a robust and flexible smart home automation and lighting control system in Scala 3. Let's embark on this journey to create a system that’s as adaptable as it is efficient! 🚀

Lesson overview

Here's a brief overview of what we'll be doing in this lesson:

  1. Basic Setup:

    • Abstract Device: Define a trait Device and create specific device classes (Light, Fan) that extend from it.
    • Factory Method: Leverage companion objects to serve as factories for generating instances of these devices.
  2. Command Pattern:

    • Purpose: Encapsulates a request as an object, facilitating parameterization, queuing, logging, and support for undoable operations.
    • Components:
      • Define a Command trait and concrete command classes (LightOnCommand, LightOffCommand).
      • Implement a RemoteControl class for command executions.
  3. Decorator Pattern:

    • Purpose: Dynamically adds functionalities to existing objects without altering their structure.
    • Components:
      • Use a decorator class (ColoredLight) to add color capabilities to a Light device.

Let’s dive into the Scala code and bring these patterns to life! 🎉

Defining Smart Home Devices

Let's start by defining the devices we'll be working with. We'll create a trait Device and extend it with concrete device classes Light and Fan.

Scala
1// Abstract Device 2trait Device: 3 def on(): Unit 4 def off(): Unit 5 6// Concrete Device: Light 7class Light extends Device: 8 def on(): Unit = println("Light is on.") 9 def off(): Unit = println("Light is off.") 10 11// Concrete Device: Fan 12class Fan extends Device: 13 private var speed: Int = 0 14 def on(): Unit = println("Fan is on.") 15 def off(): Unit = println("Fan is off.") 16 def setSpeed(speed: Int): Unit = 17 this.speed = speed 18 println(s"Fan speed set to $speed.")

This code defines the foundational elements of our smart home system:

  • The Device trait establishes a common interface with on() and off() methods.
  • The Light class extends Device with basic functionality to turn the light on and off.
  • The Fan class extends Device and adds the ability to set the fan's speed through the setSpeed() method.
Factory Method for Devices

We'll now implement factory methods using a companion object to create instances of our devices.

Scala
1// Companion Object as Factory 2object DeviceFactory: 3 def createLight(): Device = new Light() 4 def createFan(): Device = new Fan()

By introducing DeviceFactory, we encapsulate the creation of Device instances, promoting better code organization and flexibility. The factory methods createLight() and createFan() simplify object instantiation.

Applying the Command Pattern: Command Interface

Next, we'll apply the Command pattern to create flexible automation commands. First, we define a Command trait as the foundation for our system, alongside concrete command classes for light operations:

Scala
1// Command Interface 2trait Command: 3 def execute(): Unit 4 5// Concrete Command: Turn Light On 6class LightOnCommand(device: Device) extends Command: 7 def execute(): Unit = device.on() 8 9// Concrete Command: Turn Light Off 10class LightOffCommand(device: Device) extends Command: 11 def execute(): Unit = device.off()

In this snippet, we establish the Command trait and implement LightOnCommand and LightOffCommand to encapsulate turning a light on and off.

Applying the Command Pattern: RemoteControl Invoker

Next, we introduce the RemoteControl class to handle the execution of commands.

Scala
1// Invoker: RemoteControl 2class RemoteControl: 3 private var command: Option[Command] = None 4 5 def setCommand(command: Command): Unit = 6 this.command = Some(command) 7 8 def pressButton(): Unit = 9 command.foreach(_.execute())

The RemoteControl class acts as the invoker in the Command pattern:

  • It holds a reference to a Command object.
  • The setCommand() method assigns a command to the remote.
  • The pressButton() method executes the assigned command, if any.
Applying the Command Pattern: Sample Usage

We now put the command pattern into action within our main Scala program.

Scala
1@main def runCommandPattern(): Unit = 2 // Create a light instance using the factory 3 val light: Device = DeviceFactory.createLight() 4 5 // Create command instances for turning the light on and off 6 val lightOn = new LightOnCommand(light) 7 val lightOff = new LightOffCommand(light) 8 9 // Set commands in the remote control 10 val remote = new RemoteControl() 11 remote.setCommand(lightOn) 12 13 // Simulate pressing the button 14 remote.pressButton() // Output: Light is on. 15 remote.setCommand(lightOff) 16 remote.pressButton() // Output: Light is off.

This example demonstrates:

  • Creating a Light device.
  • Instantiating LightOnCommand and LightOffCommand with the light device.
  • Using the RemoteControl to set and execute commands, turning the light on and off.
Applying the Decorator Pattern: Decorator Definition

Now, we'll enhance our lighting system using the Decorator pattern.

We start with the definition of a ColoredLight decorator class to add color functionality to our lights:

Scala
1// Decorator: ColoredLight 2class ColoredLight(light: Device, color: String) extends Device: 3 def on(): Unit = 4 light.on() 5 println(s"Light color changed to $color.") 6 7 def off(): Unit = light.off()

In this simple snippet:

  • ColoredLight acts as a wrapper around a Device object (such as a Light), adding color functionality.
  • It overrides the on() method to first turn on the light and then add the color-changing functionality.
  • The off() method simply delegates to the wrapped device.
Applying the Decorator Pattern: Using the Decorator

Finally, let's apply our ColoredLight decorator to dynamically add color functionality to light devices.

Scala
1@main def useDecoratorPattern(): Unit = 2 // Create a light instance using the factory 3 val light: Device = DeviceFactory.createLight() 4 5 // Decorate the light with color 6 val redLight = new ColoredLight(light, "Red") 7 val blueLight = new ColoredLight(light, "Blue") 8 9 redLight.on() // Output: Light is on. Light color changed to Red. 10 redLight.off() // Output: Light is off. 11 blueLight.on() // Output: Light is on. Light color changed to Blue. 12 blueLight.off() // Output: Light is off.

This code shows how:

  • We create ColoredLight instances to enhance lights with color.
  • When on() is called, it turns on the light and changes its color, demonstrating dynamic addition of functionality.
Conclusion

By integrating the Command and Decorator design patterns, you've built a smart home system that is both modular and flexible. These patterns enable you to extend functionality and dynamically add features without modifying the core structure. Now it's your turn—get hands-on by implementing these patterns yourself and watch your smart home system come alive! 🌟

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.