You are here:   ArielOrtiz.com > Software Design and Architecture > Lab 3: Observer and Composite Patterns

Lab 3: Observer and Composite Patterns

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

The lab activities can be developed individually or in pairs.

The lab report must be developed individually.

  1. Create a folder called observer_composite. Inside this folder, create four files called: weather.rb, test_weather.rb, expression.rb, and test_expression.rb.

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

    # Lab 3: Observer and Composite Patterns
    # Date: 1-Feb-2011
    # Authors:
    # 456654  Anthony Stark 
    # 1160611 Thursday Rubinstein
  2. You are required to build a weather data monitoring system using the Observer pattern (this example was taken from [FREEMAN] pp. 37-60). In the weather.rb source file, define four classes:

    • WeatherData: This is the subject of our Observer pattern implementation. Include as a mixin the Observable module and add to it a set_measurements method, which takes three parameters: temperature, humidity, and pressure. This method must notify all its observing objects that a change has occurred. This means that the update method for every observer must be indirectly called with the three previously mentioned parameters. Check chapter 5 of [OLSEN] and the observer.rb documentation for more details on the Observable module.
    • CurrentConditionsDisplay: An observer that displays to the standard output the current weather conditions.
    • StatisticsDisplay: An observer that displays to the standard output weather statistics (average, maximum and minimum temperatures so far).
    • ForecastDisplay: An observer that displays to the standard output a weather forecast. If the current pressure is higher than the previous pressure, it predicts an improvement on the weather. If the current pressure is lower than the previous pressure, it predicts a cooler, rainy weather. Otherwise, it predicts that the weather will stay the same.

    Check the unit tests in the following step to see the expected display formats for each of the observers.

  3. Make sure the code that you write behaves exactly as expected by the following unit tests.

    # File: test_weather.rb
    
    require 'test/unit'
    require 'stringio'
    require './weather'
    
    class WeatherTest < Test::Unit::TestCase
      
      def setup
        @out = StringIO.new
        @old_stdout = $stdout
        $stdout = @out
        @weather_data = WeatherData.new 
      end
    
      def teardown
        $stdout = @old_stdout
      end
      
      def do_set_measurements
        @weather_data.set_measurements(80.0, 65.0, 30.4)
        @weather_data.set_measurements(82.0, 70.0, 29.2)
        @weather_data.set_measurements(78.0, 90.0, 29.2)
      end
    
      def test_current_conditions_display
        current_display = CurrentConditionsDisplay.new
        @weather_data.add_observer(current_display)
        do_set_measurements    
        assert_equal "Current conditions: 80.0F degrees and 65.0% humidity\n"  \
                     "Current conditions: 82.0F degrees and 70.0% humidity\n"  \
                     "Current conditions: 78.0F degrees and 90.0% humidity\n", \
                     @out.string
      end
    
      def test_statistics_display
        statistics_display = StatisticsDisplay.new
        @weather_data.add_observer(statistics_display)
        do_set_measurements
        assert_equal "Avg/Max/Min temperature = 80.0/80.0/80.0\n"  \
                     "Avg/Max/Min temperature = 81.0/82.0/80.0\n"  \
                     "Avg/Max/Min temperature = 80.0/82.0/78.0\n", \
                     @out.string
      end
      
      def test_forecast_display
        forecast_display = ForecastDisplay.new
        @weather_data.add_observer(forecast_display)
        do_set_measurements
        assert_equal "Forecast: Improving weather on the way!\n"       \
                     "Forecast: Watch out for cooler, rainy weather\n" \
                     "Forecast: More of the same\n",                   \
                     @out.string
      end
      
      def test_all_together    
        current_display = CurrentConditionsDisplay.new
        statistics_display = StatisticsDisplay.new
        forecast_display = ForecastDisplay.new    
        @weather_data.add_observer(current_display)
        @weather_data.add_observer(statistics_display)
        @weather_data.add_observer(forecast_display)    
        do_set_measurements
        assert_equal "Current conditions: 80.0F degrees and 65.0% humidity\n" \
                     "Avg/Max/Min temperature = 80.0/80.0/80.0\n"             \
                     "Forecast: Improving weather on the way!\n"              \
                     "Current conditions: 82.0F degrees and 70.0% humidity\n" \
                     "Avg/Max/Min temperature = 81.0/82.0/80.0\n"             \
                     "Forecast: Watch out for cooler, rainy weather\n"        \
                     "Current conditions: 78.0F degrees and 90.0% humidity\n" \
                     "Avg/Max/Min temperature = 80.0/82.0/78.0\n"             \
                     "Forecast: More of the same\n",                          \
                     @out.string    
      end    
    end
  4. In the expression.rb source file, write two classes: Operator and Operand. Together they will implement the Composite pattern. Operator is the composite class, while Operand is the leaf class. You may add a third class, that works as the component class, as described in chapter 6 of [OLSEN]. All component classes must implement an appropriate initializer method, and the to_lisp method, that returns a string representation of the corresponding component using Lisp's s-expression notation (see the unit tests to understand how this is expected to work). Given that Operator is the composite class, it must also implement the << and delete methods.

  5. The following unit tests verify the correct behavior of the requested classes in the previous point.

    # File: test_expression.rb
    
    require 'test/unit'
    require './expression'
    
    class ExpressionTest < Test::Unit::TestCase
      
      def test_simple
        operator = Operator.new('+')
        operand1 = Operand.new('x')
        operand2 = Operand.new(42)
        assert_equal '(+)', operator.to_lisp
        assert_equal 'x', operand1.to_lisp
        assert_equal '42', operand2.to_lisp
        assert_same operator, operator << operand1 << operand2
        assert_equal '(+ x 42)', operator.to_lisp
        assert_same operand1, operator.delete(operand1)    
        assert_equal '(+ 42)', operator.to_lisp
        assert_same operand2, operator.delete(operand2)
        assert_equal '(+)', operator.to_lisp
        assert_nil operator.delete operand1
        assert_equal '(+)', operator.to_lisp
      end
      
      def test_complex
        operator1 = Operator.new('*')    
        operator1 << Operand.new(4)    
        operator1 << Operand.new('a') << Operand.new('c')
        operator2 = Operator.new('*')
        operator2 << Operand.new('b') << Operand.new('b')
        operator3 = Operator.new('-')
        operator3 << operator1 << operator2
        operator4 = Operator.new('sqrt')
        operator4 << operator3
        operator5 = Operator.new('-')
        operator5 << Operand.new('b')
        operator6 = Operator.new('+')
        operator6 << operator5 << operator4
        operator7 = Operator.new('*')
        operator7 << Operand.new(2) << Operand.new('a')
        operator8 = Operator.new('/')
        operator8 << operator6 << operator7
        assert_equal '(* 4 a c)', operator1.to_lisp
        assert_equal '(* b b)', operator2.to_lisp
        assert_equal '(- (* 4 a c) (* b b))', operator3.to_lisp
        assert_equal '(sqrt (- (* 4 a c) (* b b)))', operator4.to_lisp
        assert_equal '(- b)', operator5.to_lisp
        assert_equal '(+ (- b) (sqrt (- (* 4 a c) (* b b))))', operator6.to_lisp
        assert_equal '(* 2 a)', operator7.to_lisp
        assert_equal '(/ (+ (- b) (sqrt (- (* 4 a c) (* b b)))) (* 2 a))',
                     operator8.to_lisp
        assert_same operator1, operator3.delete(operator1)
        assert_same operator5, operator6.delete(operator5)
        assert_equal '(/ (+ (sqrt (- (* b b)))) (* 2 a))', operator8.to_lisp
      end
    end
    

Deliverables

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

Due date is Thursday, February 3.

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.
© 1996-2011 by Ariel Ortiz (ariel.ortiz@itesm.mx)
Made with Django | Licensed under Creative Commons | Valid XHTML | Valid CSS