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.
The lab activities can be developed individually or in pairs.
The lab report must be developed individually.
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
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.
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
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.
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
To hand in your individual lab work, follow these instructions.
lab3_report_A0MMMMMMM.tex
, where A0MMMMMMM
is your student ID. From your LaTeX source, generate the
corresponding PDF file. That file should be called
lab3_report_A0MMMMMMM.pdf
. Place these two files in the
observer_composite
directory.
observer_composite
directory. Call this file observer_composite.zip
.
Due date is Thursday, February 3.
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. |