You are here:   ArielOrtiz.com > Software Design and Architecture > Lab 5: Command Pattern

Lab 5: Command Pattern

Objectives

During this lab session:

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

IMPORTANT NOTE: The lab activities can be developed individually or in pairs. The lab report must be developed individually.

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

  1. Create a folder called command. Inside this folder, create two files called: control.rb, and test_control.rb.

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

    # Lab 5: Command Pattern
    # Date: 22-Feb-2012
    # Authors:
    # 456654  Thursday Rubinstein 
    # 1160611 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 test_control.rb source file.

    # File: test_control.rb
    
    require 'test/unit'
    require 'stringio'
    require 'control'
    
    class ControlTest < Test::Unit::TestCase
        
      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

Deliverables

To hand in your individual lab work, follow these instructions.

Due date is Tuesday, February 28.

Evaluation

This activity will be evaluated using the following criteria:

50% Implementation of functional requirements.
50% Lab report.
DA The program and/or report was plagiarized.