Introduction

Welcome to the third lesson of our "Applying Design Patterns to Real-World Problems in Rust" course! 🎉 In this lesson, we'll explore 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 Rust. Let's embark on this journey to create a system that's as adaptable as it is efficient! 🚀

Lesson Overview

In this lesson, we'll dive deep into implementing the Command and Decorator patterns. Here's what we'll cover:

  1. Define Smart Home Appliances: Create a simple Appliance struct with methods to control devices.
  2. Implement the Command Pattern: Develop a Command trait, concrete command structs (TurnOnCommand, TurnOffCommand), and an AutomationController invoker to manage and execute commands.
  3. Implement the Light Trait and Decorators: Define a Light trait and create decorators (BasicLight, DimmableLight, ColorChangingLight) to enhance light functionality dynamically.
  4. Apply the Patterns in Rust: Demonstrate the usage of Command and Decorator patterns in a Rust application and show how to combine them to control smart devices effectively.

Let's dive into the Rust code and bring these patterns to life! 🦀

Defining Smart Home Appliances

We'll start by defining the appliances we'll control in our smart home system. We'll create a simple Appliance struct with methods to turn the appliance on and off.

pub struct Appliance;

impl Appliance {
    pub fn on(&self) {
        println!("Appliance turned on.");
    }

    pub fn off(&self) {
        println!("Appliance turned off.");
    }
}

In this code:

  • Appliance Struct: Represents a generic smart appliance.
  • on and off Methods: Simulate turning the appliance on and off.

This struct serves as the receiver in the Command pattern, which we'll explore next.

Applying the Command Pattern: Command Trait and Commands

The Command pattern encapsulates a request as an object, allowing us to parameterize clients with queues, requests, and operations. It also enables undoable operations and provides a higher level of abstraction for actions.

First, we'll define a Command trait and implement concrete commands that interact with the Appliance.

pub trait Command {
    fn execute(&self, appliance: &Appliance);
}

pub struct TurnOnCommand;

impl Command for TurnOnCommand {
    fn execute(&self, appliance: &Appliance) {
        appliance.on();
    }
}

pub struct TurnOffCommand;

impl Command for TurnOffCommand {
    fn execute(&self, appliance: &Appliance) {
        appliance.off();
    }
}

Here:

  • Command Trait: Defines an execute method that takes a reference to an Appliance.
  • Concrete Commands:
    • TurnOnCommand: Calls the on method on the appliance.
    • TurnOffCommand: Calls the off method on the appliance.

These commands encapsulate the actions to be performed on the appliance.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal