Motivating Problem
Whenever the display feature is called, retrieve the current
values of temperature, humidity, and/or pressure via the
weather data reference. 3 of 36
Implementing the First Design (1)
class WEATHER_DATA create make feature — Data
temperature: REAL humidity: REAL pressure: REAL
feature — Queries correct_limits(t,p,h: REAL): BOOLEAN
ensure
Result implies -36 <=t and t <= 60 Result implies 50 <= p and p <= 110 Result implies 0.8 <= h and h <= 100
feature -- Commands make (t, p, h: REAL)
require
correct limits(temperature, pressure, humidity)
ensure
temperature = t and pressure = p and humidity = h
invariant
correct limits(temperature, pressure, humidity)
end
Observer Design Pattern Event-Driven Design
EECS3311 A: Software Design Winter 2020
CHEN-WEI WANG
● A weather station maintains weather data such as temperature, humidity, and pressure.
● Various kinds of applications on these weather data should regularly update their displays:
○ Forecast: if expecting for rainy weather due to reduced pressure.
○ Condition: temperature in celsius and humidity in percentages.
○ Statistics: minimum/maximum/average measures of temperature. 2 of 36
4 of 36
First Design: Weather Station
Implementing the First Design (2.1)
class FORECAST create make feature -- Attributes
current_pressure: REAL last_pressure: REAL weather_data: WEATHER_DATA
feature -- Commands make(wd: WEATHER_DATA)
ensure weather data = wd update
do last_pressure := current_pressure current_pressure := weather_data.pressure
end
display
do update
if current_pressure > last_pressure then
print(“Improving weather on the way!%N”) elseif current_pressure = last_pressure then
print(“More of the same%N”)
else print(“Watch out for cooler, rainy weather%N”) end
end
end
5 of 36
Implementing the First Design (2.3)
class STATISTICS create make feature — Attributes
weather_data: WEATHER_DATA current_temp: REAL
max, min, sum_so_far: REAL num_readings: INTEGER
feature — Commands make(wd: WEATHER_DATA)
ensure weather data = wd update
do current_temp := weather_data.temperature — Update min, max if necessary.
end
display
do update
print(“Avg/Max/Min temperature = “)
print(sum_so_far / num_readings + “/” + max + “/” min + “%N”)
end end
7 of 36
Implementing the First Design (2.2)
class CURRENT_CONDITIONS create make feature — Attributes
temperature: REAL humidity: REAL weather_data: WEATHER_DATA
feature — Commands make(wd: WEATHER_DATA)
ensure weather data = wd update
do temperature := weather_data.temperature humidity := weather_data.humidity
end
display
do update
io.put_string(“Current Conditions: “)
io.put_real (temperature) ; io.put_string (” degrees C and “)
io.put_real (humidity) ; io.put_string (” percent humidity%N”) end
end
6 of 36
Implementing the First Design (3)
1 class WEATHER_STATION create make 2 feature — Attributes
3
4
5 feature — Commands 6 make
cc: CURRENT_CONDITIONS ; fd: FORECAST ; sd: STATISTICS wd: WEATHER_DATA
7 8 9
do
create wd.make (9, 75, 25)
create cc.make (wd) ; create fd.make (wd) ; create sd.make(wd)
wd.set_measurements (15, 60, 30.4) cc.display ; fd.display ; sd.display cc.display ; fd.display ; sd.display
wd.set_measurements (11, 90, 20) cc.display ; fd.display ; sd.display
10
11
12
13
14
15
16 end 17 end
L14: Updates occur on cc, fd, sd even with the same data. 8 of 36
First Design: Good Design?
Observer Pattern: Weather Station
● Each application (CURRENT CONDITION, FORECAST, STATISTICS) cannot know when the weather data change.
⇒ All applications have to periodically initiate updates in order to keep the display results up to date.
∵ Each inquiry of current weather data values is a remote call.
∴ Waste of computing resources (e.g., network bandwidth)
when there are actually no changes on the weather data. ● To avoid such overhead, it is better to let:
9 of 36
○ Each application is subscribed/attached/registered to the weather data.
○ The weather data publish/notify new changes.
⇒ Updates on the application side occur only when necessary . Observer Pattern: Architecture
11 of 36
Implementing the Observer Pattern (1.1)
class SUBJECT create make feature — Attributes
observers : LIST[OBSERVER] feature — Commands
make
do create {LINKED_LIST[OBSERVER]} observers.make ensure no observers: observers.count = 0 end
feature — Invoked by an OBSERVER
attach (o: OBSERVER) — Add ‘o’ to the observers
require not yet attached: not observers.has (o)
ensure is attached: observers.has (o) end detach (o: OBSERVER) — Add ‘o’ to the observers
require currently attached: observers.has (o)
ensure is attached: not observers.has (o) end feature — invoked by a SUBJECT
notify — Notify each attached observer about the update. do across observers as cursor loop cursor.item.update end ensure all views updated:
across observers as o all o.item.up_to_date_with_subject end end
end
12 of 36
● Observer (publish-subscribe) pattern: one-to-many relation. ○ Observers (subscribers) are attached to a subject (publisher).
○ The subject notify its attached observers about changes.
● Some interchangeable vocabulary: ○ subscribe ≈ attach ≈ register
○ unsubscribe ≈ detach ≈ unregister ○ publish ≈ notify
○ handle ≈ update 10 of 36
Implementing the Observer Pattern (1.2)
13 of 36
class WEATHER_DATA
inherit SUBJECT rename make as make subject end create make
feature — data available to observers
temperature: REAL
humidity: REAL
pressure: REAL
correct_limits(t,p,h: REAL): BOOLEAN
feature — Initialization make (t, p, h: REAL)
do
make subject — initialize empty observers
set_measurements (t, p, h) end
feature — Called by weather station set_measurements(t, p, h: REAL)
require correct_limits(t,p,h) invariant
correct limits(temperature, pressure, humidity)
end
Implementing the Observer Pattern (2.2)
class FORECAST inherit OBSERVER feature — Commands
make(a_weather_data: WEATHER_DATA) do weather_data := a_weather_data
weather data.attach (Current)
ensure weather_data = a_weather_data weather data.observers.has (Current)
end
feature — Queries
up_to_date_with_subject: BOOLEAN ensure then
Result = current_pressure = weather_data.pressure update
do — Same as 1st design; Called only on demand
end
display
do — No need to update; Display contents same as in 1st design
end end
15 of 36
Implementing the Observer Pattern (2.1)
deferred class
OBSERVER
feature — To be effected by a descendant up_to_date_with_subject: BOOLEAN
— Is this observer up to date with its subject?
deferred end
update
— Update the observer’s view of ‘s’
deferred
ensure
up_to_date_with_subject: up_to_date_with_subject end
end
Each effective descendant class of OBSERVER should: ○ Define what weather data are required to be up-to-date. ○ Define how to update the required weather data.
14 of 36
Implementing the Observer Pattern (2.3)
class CURRENT_CONDITIONS inherit OBSERVER
feature — Commands
make(a_weather_data: WEATHER_DATA) do weather_data := a_weather_data
weather data.attach (Current)
ensure weather_data = a_weather_data weather data.observers.has (Current)
end
feature — Queries
up_to_date_with_subject: BOOLEAN
ensure then Result = temperature = weather_data.temperature and
humidity = weather_data.humidity
update
do — Same as 1st design; Called only on demand
end
display
do — No need to update; Display contents same as in 1st design
end end
16 of 36
Implementing the Observer Pattern (2.4)
class STATISTICS inherit OBSERVER feature — Commands
make(a_weather_data: WEATHER_DATA) do weather_data := a_weather_data
weather data.attach (Current)
ensure weather_data = a_weather_data weather data.observers.has (Current)
end
feature — Queries
up_to_date_with_subject: BOOLEAN ensure then
Result = current_temperature = weather_data.temperature update
do — Same as 1st design; Called only on demand
end
display
do — No need to update; Display contents same as in 1st design
end end
17 of 36
Observer Pattern: Limitation? (1)
● The observer design pattern is a reasonable solution to building a one-to-many relationship: one subject (publisher) and multiple observers (subscribers).
● But what if a many-to-many relationship is required for the application under development?
○ Multiple weather data are maintained by weather stations.
○ Each application observes all these weather data.
○ But, each application still stores the latest measure only. e.g., the statistics app stores one copy of temperature
○ Whenever some weather station updates the temperature of its associated weather data, all relevant subscribed applications (i.e., current conditions, statistics) should update their temperatures.
● How can the observer pattern solve this general problem? ○ Each weather data maintains a list of subscribed applications. ○ Each application is subscribed to multiple weather data.
19 of 36
Implementing the Observer Pattern (3)
1 class WEATHER_STATION create make 2 feature — Attributes
3
4
5 feature — Commands 6 make
cc: CURRENT_CONDITIONS ; fd: FORECAST ; sd: STATISTICS wd: WEATHER_DATA
7 8 9
10 11
do
create wd.make (9, 75, 25)
create cc.make (wd) ; create fd.make (wd) ; create sd.make(wd)
wd.set_measurements (15, 60, 30.4) wd.notify
cc.display ; fd.display ; sd.display cc.display ; fd.display ; sd.display
wd.set_measurements (11, 90, 20) wd.notify
cc.display ; fd.display ; sd.display
12
13
14
15
16
17
18 end 19 end
L13: cc, fd, sd make use of “cached” data values. 18 of 36
Observer Pattern: Limitation? (2)
What happens at runtime when building a relationship using the observer pattern?
wd1: WEATHER_DATA wd2: WEATHER_DATA
…
wdm 1: WEATHER_DATA wdm: WEATHER_DATA
application1
application2
…
applicationn
Graph complexity, with m subjects and n observers? [ O( m ⋅ n ) ] 20 of 36
many-to-many
Event-Driven Design (1)
Here is what happens at runtime when building a relationship using the event-driven design.
wd1: WEATHER_DATA wd2: WEATHER_DATA
…
wdn 1: WEATHER_DATA wdn: WEATHER_DATA
publish subscribe change_on_temperature: EVENT
application1
application2 …
applicationn 1 applicationn
Graph complexity, with m subjects and n observers? Additional cost by adding a new subject?
Additional cost by adding a new observer?
Additional cost by adding a new event type?
21 of 36
[O( m + n )] [O(1)] [O(1)] [O(m + n)]
many-to-many
Event-Driven Design: Example
Darlington Nuclear Generating System (by Ontario Power Generation)
https://www.opg.com
23 of 36
Image Source:
Event-Driven Design (2)
In an event-driven design :
● Each variable being observed (e.g., temperature,
humidity, pressure) is called a monitored variable.
e.g., A (i.e., the subject) has its temperature and pressure being monitored by a shutdown system (i.e., an observer): as soon as values of these monitored variables exceed the normal threshold, the SDS will be notified and react by shutting down the plant.
● Each monitored variable is declared as an event :
○ An observer is attached/subscribed to the relevant events.
nuclear power plant
● CURRENTCONDITIONattachedtoeventsfortemperature,humidity. ● FORECASTonlysubscribedtotheeventforpressure.
● STATISTICSonlysubscribedtotheeventfortemperature.
○ A subject notifies/publishes changes to the relevant events. 22 of 36
Event-Driven Design: Implementation
● Requirements for implementing an event-driven design are:
1. When an observer object is subscribed to an event, it attaches:
1.1 The reference/pointer to an update operation
Such reference/pointer is used for executions.
1.2 Itself (i.e., the context object for invoking the update operation)
2. For the subject object to publish an update to the event, it:
2.1 Iterates over its (previously) attached operation references/pointers 2.2 Invokes these operations, which update the corresponding observers
● Both requirements can be satisfied by Eiffel and Java.
● We will compare how an event-driven design for the weather
station problems is implemented in Eiffel and Java.
⇒ It is more convenient to implement such design in Eiffel.
delayed
24 of 36
Event-Driven Design in Java (1)
1 2 3 4 5 6 7 8 9
10 11
12 13 14 15
public class Event {
Hashtable
Event-Driven Design in Eiffel (1)
1 class EVENT [ARGUMENTS -> TUPLE ] 2 create make
3 feature — Initialization
4
5
6 feature
actions: LINKED_LIST[PROCEDURE[ARGUMENTS]] make do create actions.make end
7 8 9
subscribe (an_action: PROCEDURE[ARGUMENTS])
require action_not_already_subscribed: not actions.has(an_action) do actions.extend (an_action)
ensure action_subscribed: action.has(an_action) end
loop actions.item.call (args) ; actions.forth end
10
11
12
13
14 end 15 end
publish (args: ARGUMENTS)
do from actions.start until actions.after
● L1 constrains the generic parameter ARGUMENTS: any class that instantiates ARGUMENTS must be a descendant of TUPLE.
● L4: The type PROCEDURE encapsulates both the context object and the reference/pointer to some update operation.
29 of 36
Event-Driven Design in Eiffel (3)
1 2 3 4 5 6 7 8 9
10
11
12
13
14
15
● retrieves the pointer to cmd and its context object. ● L6 ≈
● Contrast L6 with L8–11 in Java class CurrentConditions. 31 of 36
. . . (agent Current .update temperature)
class CURRENT_CONDITIONS create make
feature — Initialization
make(wd: WEATHER_DATA) do
wd.change on temperature.subscribe (agent update_temperature)
wd.change on humidity.subscribe (agent update_humidity) end
feature
temperature: REAL
humidity: REAL
update_temperature (t: REAL) do temperature := t end update_humidity (h: REAL) do humidity := h end display do … end
end
agent cmd
Event-Driven Design in Eiffel (2)
1 2 3 4 5 6 7 8
9
10
11
12
13
14
15
16
17
18
19
30 of 36
class WEATHER_DATA create make
feature — Measurements
temperature: REAL ; humidity: REAL ; pressure: REAL correct_limits(t,p,h: REAL): BOOLEAN do … end
make (t, p, h: REAL) do … end
feature — Event for data changes
: EVENT[TUPLE[REAL]]once create Result end
: EVENT[TUPLE[REAL]]once create Result end : EVENT[TUPLE[REAL]]once create Result end
feature — Command set_measurements(t, p, h: REAL)
require correct_limits(t,p,h)
do temperature := t ; pressure := p ; humidity := h
.publish ([t]) .publish ([p]) .publish ([h])
end
invariant correct_limits(temperature, pressure, humidity) end
change on temperature
change on humidity
change on pressure
change on temperature
change on humidity
change on pressure
Event-Driven Design in Eiffel (4)
1 2 3 4 5 6 7 8 9
10 11 12
L6 invokes
wd.change on temperature.subscribe(
agent cc.update temperature) wd.change on temperature.publish([15])
which in turn invokes
L7 invokes
cc.update temperature(15)
32 of 36
class WEATHER_STATION create make feature
cc: CURRENT_CONDITIONS make
do create wd.make (9, 75, 25)
create cc.make (wd)
wd.set measurements (15, 60, 30.4) cc.display
wd.set measurements (11, 90, 20) cc.display
end end
Event-Driven Design: Eiffel vs. Java
● ○ Java, in the Event class:
● ○ Eiffel, in the EVENT class:
○ Java, in the CurrentConditions class constructor:
○ Eiffel, in the CURRENT CONDITIONS class construction:
⇒ Eiffel’s type system has been better thought-out for . 33 of 36
Hashtable