Card 08/ 09

ExerciseDifficulty: Advanced2 min

Practice: A Temperature Sensor With Displays That Come and Go

Displays subscribe and unsubscribe while readings arrive.

A temperature sensor is a natural Observer, and this exercise asks you to find the subscriber that outlives its use. A sensor notifies attached displays of each reading. A maximum display is attached for the whole run. A room shows the current reading on its own display while it is open.

py
class Room:
    def open(self) -> None:
        self._sensor.attach(self._display)
        self.is_open = True

    def close(self) -> None:
        self.is_open = False

The sensor records 19.5, then the room opens and it records 21.0, then the room closes and it records 20.0.

  1. Which displays print each reading?
  2. How many displays are attached at the end?
  3. Fix close.
Show a solution
py
from typing import Protocol


class Display(Protocol):
    def reading(self, celsius: float) -> None: ...


class Sensor:
    def __init__(self) -> None:
        self._displays: list[Display] = []

    def attach(self, display: Display) -> None:
        self._displays.append(display)

    def detach(self, display: Display) -> None:
        self._displays.remove(display)

    def record(self, celsius: float) -> None:
        for display in self._displays:
            display.reading(celsius)

    @property
    def display_count(self) -> int:
        return len(self._displays)


class CurrentDisplay:
    def reading(self, celsius: float) -> None:
        print(f"  now: {celsius} C")


class MaxDisplay:
    def __init__(self) -> None:
        self.highest = float("-inf")

    def reading(self, celsius: float) -> None:
        self.highest = max(self.highest, celsius)
        print(f"  max: {self.highest} C")


class Room:
    def __init__(self, sensor: Sensor) -> None:
        self._sensor = sensor
        self._display = CurrentDisplay()
        self.is_open = False

    def open(self) -> None:
        self._sensor.attach(self._display)
        self.is_open = True

    def close(self) -> None:
        self.is_open = False


sensor = Sensor()
peak = MaxDisplay()
sensor.attach(peak)
print("19.5 with one display")
sensor.record(19.5)

room = Room(sensor)
room.open()
print("21.0 with the room open")
sensor.record(21.0)

room.close()
print("20.0 with the room closed")
sensor.record(20.0)
print("displays attached:", sensor.display_count)
OutputRan with python:3.12-slim (Python 3.12.13)
19.5 with one display
  max: 19.5 C
21.0 with the room open
  max: 21.0 C
  now: 21.0 C
20.0 with the room closed
  max: 21.0 C
  now: 20.0 C
displays attached: 2
The room is closed, and its display still prints 20.0.

The maximum display prints all three readings. The room's display prints 21.0, as expected, and also 20.0 after the room closed, and two displays are still attached at the end. close changes a flag that the sensor never reads, and the display stays on the list.

The fix is the matching detach, so the room undoes in close exactly what it did in open.

py
def close(self) -> None:
    self._sensor.detach(self._display)

With it, the 20.0 reading reaches only the maximum display, and one display is attached at the end. The general check is to look for every attach and find the detach that answers it.