Card 07/ 08

ExerciseDifficulty: Advanced2 min

Practice: Light and Dark Themes for a Widget Kit

Buttons, checkboxes and sliders, in a light family and a dark one.

A widget kit with themes is a classic Abstract Factory, and this exercise asks you to apply the pattern to one and then price the two changes the pattern treats differently. A settings screen shows a button, a checkbox and a slider. The kit has a light theme and a dark theme, and the screen must never show a light button beside a dark slider.

  1. Name the abstract factory, its creation methods, and the concrete factories.
  2. Write settings_screen(theme) so that it never names a light or dark class.
  3. Count the new and edited classes for adding a toggle switch, a fourth kind of widget.
  4. Count them for adding a high-contrast theme instead.
Show a solution
The widget kit as Abstract Factory
py
from abc import ABC, abstractmethod


class Button(ABC):
    @abstractmethod
    def render(self) -> str: ...


class Checkbox(ABC):
    @abstractmethod
    def render(self) -> str: ...


class Slider(ABC):
    @abstractmethod
    def render(self) -> str: ...


class LightButton(Button):
    def render(self) -> str:
        return "[ OK ] dark text on white"


class LightCheckbox(Checkbox):
    def render(self) -> str:
        return "[x] dark tick on white"


class LightSlider(Slider):
    def render(self) -> str:
        return "--o-- grey track on white"


class DarkButton(Button):
    def render(self) -> str:
        return "[ OK ] white text on black"


class DarkCheckbox(Checkbox):
    def render(self) -> str:
        return "[x] white tick on black"


class DarkSlider(Slider):
    def render(self) -> str:
        return "--o-- white track on black"


class ThemeFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button: ...

    @abstractmethod
    def create_checkbox(self) -> Checkbox: ...

    @abstractmethod
    def create_slider(self) -> Slider: ...


class LightTheme(ThemeFactory):
    def create_button(self) -> Button:
        return LightButton()

    def create_checkbox(self) -> Checkbox:
        return LightCheckbox()

    def create_slider(self) -> Slider:
        return LightSlider()


class DarkTheme(ThemeFactory):
    def create_button(self) -> Button:
        return DarkButton()

    def create_checkbox(self) -> Checkbox:
        return DarkCheckbox()

    def create_slider(self) -> Slider:
        return DarkSlider()


def settings_screen(theme: ThemeFactory) -> None:
    for widget in [theme.create_button(), theme.create_checkbox(), theme.create_slider()]:
        print(widget.render())


settings_screen(LightTheme())
settings_screen(DarkTheme())
OutputRan with python:3.12-slim (Python 3.12.13)
[ OK ] dark text on white
[x] dark tick on white
--o-- grey track on white
[ OK ] white text on black
[x] white tick on black
--o-- white track on black
`settings_screen` asks the theme for each widget.

ThemeFactory is the abstract factory, with create_button, create_checkbox and create_slider. LightTheme and DarkTheme are the concrete factories.

A toggle switch is the expensive change: three new classes, Toggle, LightToggle and DarkToggle, and three edited, since ThemeFactory, LightTheme and DarkTheme each gain create_toggle. A high-contrast theme is the cheap one: four new classes, the factory and its three widgets, and nothing edited.