In Part 1, we looked at the interfaces that define the Observer pattern in Delphi. But how do these observers actually get created and managed within a standard VCL component? The answer lies in the base of the entire VCL/FMX hierarchy: TComponent.
Delphi engineers faced a challenge: they wanted every component to be capable of supporting observers, but they didn't want to bloat the memory footprint of simple components that never use them. The solution is a "Lazy Initialization" pattern.
Inside TComponent, the FObservers field is only instantiated the first time someone touches the Observers property. This is handled by a private getter and an initialization method:
TComponent
FObservers
Observers
procedure TComponent.InitObservers; begin FObservers := TObservers.Create; // Link the manager back to the component's virtual methods FObservers.OnCanObserve := CanObserve; FObservers.OnObserverAdded := ObserverAdded; end; function TComponent.GetObservers: TObservers; begin if FObservers = nil then InitObservers; Result := FObservers; end; // Exposing the property property Observers: TObservers read GetObservers;
This means that if you have a thousand components on a form and only five are using the TObservers machinery, only those five will incur the memory cost of a TObservers instance.
TObservers
Notice the two assignments in InitObservers. These connect the generic TObservers engine to the component's specific logic. This creates a callback loop where the engine asks the component: "Are you allowed to be observed for this specific ID?"
InitObservers
Because FObservers is an object, it must be explicitly freed to avoid memory leaks. Delphi handles this automatically in the TComponent.Destroy destructor. Even if the observers were never initialized, the .Free call is safe because it checks for nil internally.
TComponent.Destroy
.Free
nil
destructor TComponent.Destroy; begin ... // Clean up the observer engine FObservers.Free; ... end;
This lifecycle ensures that the observer engine is a "first-class citizen"—it lives as long as the component does, ensuring that any active data links are cleanly disconnected when the UI element is removed from memory.
Now that we understand how the engine is born and dies, we need to look at the "gatekeeper" logic that decides which components can participate in which observation types.
In Part 3: We explore the "Gatekeeper" methods: CanObserve and ObserverAdded, and how they define the capabilities of an observable component.
CanObserve
ObserverAdded
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