In 2017, with the release of Delphi 10.2 Tokyo, Embarcadero introduced a specialized implementation of the Observer pattern into the System.Classes unit. While it has been in the wild for 9 years, it remains a "hidden" architecture for many, primarily because it serves as the invisible engine behind LiveBindings. Other than live bindings, you can also use the Observer pattern as a way to update component settings to the Windows registry, an .ini file, or persist it elsewhere.
System.Classes
However, this architecture isn't just for the internal framework. It is a powerful tool for any developer looking to decouple UI controls from business logic or create self-synchronizing components.
Unlike a simple notification event, IObserver is designed to be a stateful manager. It doesn't just watch for a change; it participates in the lifecycle of the object it observes.
IObserver = interface ['{B03253D8-7720-4B68-B10A-E3E79B91ECD3}'] procedure Removed; function GetActive: Boolean; procedure SetActive(Value: Boolean); property Active: Boolean read GetActive write SetActive; // ... Toggle events and properties end;
Crucially, the interface includes an Active property. This allows a subject to "silence" an observer without having to unregister it—a vital feature when you need to programmatically update a control without triggering a feedback loop of change notifications.
Active
Delphi makes a rigid distinction between how observers are distributed. This is reflected in two descendant interfaces:
TEdit
Unfortunately, in the Delphi RTL, there's a bug in TObservers.GetSingleCastObserver. Here's what's in the loop in GetSingleCastObserver:
TObservers.GetSingleCastObserver
for I := 0 to LList.Count - 1 do begin if Supports(LList.Items[0], ISingleCastObserver, LObserver) then ... end;
The RTL loops through the entire list, intending to look for an implementation of a ISingleCastObserver, but it always looks at the first item, instead of every item. It's a single letter change, and the bug, RSS-1873: Indexing problem in TObservers.GetSingleCastObserver was filed back in 23 Sep 2024, and it's now 2026, and it's still not fixed.
Managing these interfaces manually would be a nightmare. Instead, Delphi provides the TObservers class. This class acts as a central registry within a component, mapping specific Integer IDs to lists of observers.
Integer
TObservers = class public procedure AddObserver(const ID: Integer; const AIntf: IInterface); procedure RemoveObserver(const ID: Integer; const AIntf: IInterface); function IsObserving(const ID: Integer): Boolean; end;
This ID-based system (using constants from TObserverMapping) allows a single component to support multiple types of observation—such as tracking a value, a position, or an edit state—all through the same management object.
TObserverMapping
In the next installment, we’ll look at how TComponent brings this engine to life through lazy initialization and the component lifecycle.
In Part 2: We dive into the lazy-loading of the Observers property and how Delphi ensures zero memory overhead for components that don't use them.
Observers
This article discusses the new Delphi 8 property access specifiers.