S/W Design and Architecture

Command Pattern

Objectives

During this activity:

This activity helps students develop the following skills, values and attitudes: ability to analyze and synthesize, capacity for identifying and solving problems, and efficient use of computer systems.


Activity Description

This activity can be developed individually or in pairs.

This example was taken from [FREEMAN] pp. 191-223.

  1. Create a directory called command and a subdirectory src within it. Inside the src folder create two files called: control.rb, and control_test.rb.

    Both Ruby source files must start with a comment containing title, date, and the authors' personal information. For example:

    # Command Pattern
    # Date: 18-Feb-2019
    # Authors:
    #          A00456654 Thursday Rubinstein 
    #          A01160611 Anthony Stark
    
  2. You are required to write part of the software for the control remote shown in the following image, using the Command pattern.

    A remote control

    You are given the following Ruby code. Place it in the control.rb source file and study it carefully.

    # File: control.rb
    
    class RemoteControlWithUndo
    
      def initialize
        @on_commands = []
        @off_commands = []
        no_command = NoCommand.new
        7.times do
          @on_commands << no_command
          @off_commands << no_command
        end
        @undo_command = no_command
      end
    
      def set_command(slot, on_command, off_command)
        @on_commands[slot] = on_command
        @off_commands[slot] = off_command
      end
    
      def on_button_was_pushed(slot)
        @on_commands[slot].execute
        @undo_command = @on_commands[slot]
      end
    
      def off_button_was_pushed(slot)
        @off_commands[slot].execute
        @undo_command = @off_commands[slot]
      end
    
      def undo_button_was_pushed()
        @undo_command.undo
      end
    
      def inspect
        string_buff = ["\n------ Remote Control -------\n"]
        @on_commands.zip(@off_commands) \
            .each_with_index do |commands, i|
          on_command, off_command = commands
          string_buff << \
          "[slot #{i}] #{on_command.class}  " \
            "#{off_command.class}\n"
        end
        string_buff << "[undo] #{@undo_command.class}\n"
        string_buff.join
      end
    
    end
    
    class NoCommand
    
      def execute
      end
    
      def undo
      end
    
    end
    
    class Light
    
      attr_reader :level
    
      def initialize(location)
        @location = location
        @level = 0
      end
    
      def on
        @level = 100
        puts "Light is on"
      end
    
      def off
        @level = 0
        puts "Light is off"
      end
    
      def dim(level)
        @level = level
        if level == 0
          off
        else
          puts "Light is dimmed to #{@level}%"
        end
      end
    
    end
    
    class CeilingFan
    
      # Access these constants from outside this class as
      # CeilingFan::HIGH, CeilingFan::MEDIUM, and so on.
      HIGH   = 3
      MEDIUM = 2
      LOW    = 1
      OFF    = 0
    
      attr_reader :speed
    
      def initialize (location)
        @location = location
        @speed = OFF
      end
    
      def high
        @speed = HIGH
        puts "#{@location} ceiling fan is on high"
      end
    
      def medium
        @speed = MEDIUM
        puts "#{@location} ceiling fan is on medium"
      end
    
      def low
        @speed = LOW
        puts "#{@location} ceiling fan is on low"
      end
    
      def off
        @speed = OFF
        puts "#{@location} ceiling fan is off"
      end
    
    end
    

    Write the following command classes placing them in the control.rb source file: LightOnCommand, LightOffCommand, CeilingFanHighCommand, CeilingFanMediumCommand, and CeilingFanOffCommand. All these classes should have these three methods: initialize, execute and undo. Check the unit tests in the next step to understand the expected behavior of each method.

  3. The following unit tests verify the correct behavior of your classes. Place the test class in the control_test.rb source file.

    # File: control_test.rb
    
    require 'minitest/autorun'
    require 'stringio'
    require 'control'
    
    class ControlTest < Minitest::Test
    
      def setup
        set_stdout
        @rc = RemoteControlWithUndo.new
        set_light
        set_fan
      end
    
      def teardown
        reset_stdout
      end
    
      def set_stdout
        @out = StringIO.new
        @old_stdout = $stdout
        $stdout = @out
      end
    
      def reset_stdout
        $stdout = @old_stdout
      end
    
      def set_light
        light = Light.new("Living Room")
        light_on = LightOnCommand.new(light)
        light_off = LightOffCommand.new(light)
        @rc.set_command(0, light_on, light_off)
      end
    
      def set_fan
        fan = CeilingFan.new("Living Room")
        fan_medium = CeilingFanMediumCommand.new(fan)
        fan_high = CeilingFanHighCommand.new(fan)
        fan_off = CeilingFanOffCommand.new(fan)
        @rc.set_command(1, fan_medium, fan_off)
        @rc.set_command(2, fan_high, fan_off)
      end
    
      def test_light
        @rc.on_button_was_pushed(0)
        @rc.off_button_was_pushed(0)
        p @rc
        @rc.undo_button_was_pushed
        @rc.off_button_was_pushed(0)
        @rc.on_button_was_pushed(0)
        p @rc
        @rc.undo_button_was_pushed
        assert_equal                                                \
          "Light is on\n"                                           \
          "Light is off\n"                                          \
          "\n------ Remote Control -------\n"                       \
          "[slot 0] LightOnCommand  LightOffCommand\n"              \
          "[slot 1] CeilingFanMediumCommand  CeilingFanOffCommand\n"\
          "[slot 2] CeilingFanHighCommand  CeilingFanOffCommand\n"  \
          "[slot 3] NoCommand  NoCommand\n"                         \
          "[slot 4] NoCommand  NoCommand\n"                         \
          "[slot 5] NoCommand  NoCommand\n"                         \
          "[slot 6] NoCommand  NoCommand\n"                         \
          "[undo] LightOffCommand\n\n"                              \
          "Light is on\n"                                           \
          "Light is off\n"                                          \
          "Light is on\n"                                           \
          "\n------ Remote Control -------\n"                       \
          "[slot 0] LightOnCommand  LightOffCommand\n"              \
          "[slot 1] CeilingFanMediumCommand  CeilingFanOffCommand\n"\
          "[slot 2] CeilingFanHighCommand  CeilingFanOffCommand\n"  \
          "[slot 3] NoCommand  NoCommand\n"                         \
          "[slot 4] NoCommand  NoCommand\n"                         \
          "[slot 5] NoCommand  NoCommand\n"                         \
          "[slot 6] NoCommand  NoCommand\n"                         \
          "[undo] LightOnCommand\n\n"                               \
          "Light is off\n", @out.string
      end
    
      def test_fan
        @rc.on_button_was_pushed(1)
        @rc.off_button_was_pushed(1)
        p @rc
        @rc.undo_button_was_pushed
        @rc.on_button_was_pushed(2)
        p @rc
        @rc.undo_button_was_pushed
        assert_equal                                                \
          "Living Room ceiling fan is on medium\n"                  \
          "Living Room ceiling fan is off\n"                        \
          "\n------ Remote Control -------\n"                       \
          "[slot 0] LightOnCommand  LightOffCommand\n"              \
          "[slot 1] CeilingFanMediumCommand  CeilingFanOffCommand\n"\
          "[slot 2] CeilingFanHighCommand  CeilingFanOffCommand\n"  \
          "[slot 3] NoCommand  NoCommand\n"                         \
          "[slot 4] NoCommand  NoCommand\n"                         \
          "[slot 5] NoCommand  NoCommand\n"                         \
          "[slot 6] NoCommand  NoCommand\n"                         \
          "[undo] CeilingFanOffCommand\n\n"                         \
          "Living Room ceiling fan is on medium\n"                  \
          "Living Room ceiling fan is on high\n"                    \
          "\n------ Remote Control -------\n"                       \
          "[slot 0] LightOnCommand  LightOffCommand\n"              \
          "[slot 1] CeilingFanMediumCommand  CeilingFanOffCommand\n"\
          "[slot 2] CeilingFanHighCommand  CeilingFanOffCommand\n"  \
          "[slot 3] NoCommand  NoCommand\n"                         \
          "[slot 4] NoCommand  NoCommand\n"                         \
          "[slot 5] NoCommand  NoCommand\n"                         \
          "[slot 6] NoCommand  NoCommand\n"                         \
          "[undo] CeilingFanHighCommand\n\n"                        \
          "Living Room ceiling fan is on medium\n", @out.string
      end
    
    end
    
  4. Write and generate your program’s documentation as described in: Documenting Ruby Programs.

Deliverables

Create a compressed tarball file with the full contents of the command directory (including the generated HTML documentation in the doc subdirectory). Call this file command.tgz. From a terminal, you can use the following command to create this file (make sure to run it at the same level where the command folder resides):

tar czf command.tgz command

Upload Instructions

To deliver the command.tgz file, please provide the following information:

Request PIN

Only one team member needs to upload the file.

Due date is Monday, February 18.

Evaluation

This activity will be evaluated using the following criteria:

50% Implementation of program requirements.
50% Documentation.
1 The program and/or documentation was plagiarized.