In the previous article, we saw how TComponent initializes the observer engine. However, simply having an engine doesn't mean a component knows how to use it. To manage this, Delphi uses a "Gatekeeper" pattern consisting of two key virtual methods.

The Default "No"

By default, a component is a closed door. If you attempt to attach an observer to a generic TComponent, it will fail. This is because the default implementation of the check-method is strictly pessimistic:

function TComponent.CanObserve(const ID: Integer): Boolean;
begin
  Result := False;
end;

procedure TComponent.ObserverAdded(const ID: Integer; const Observer: IObserver);
begin
  // Default: do nothing
end;

This design ensures that the observer engine doesn't waste resources or cause unexpected side effects by attaching logic to components that aren't specifically programmed to handle it.

CanObserve: The Capability Check

When a developer wants to make a component "observable," they must override CanObserve. This method acts as a capability negotiation. It uses specific IDs (defined in TObserverMapping) to tell the engine what interfaces it supports.

Common IDs include:

  • EditLinkID (1): Supports data editing and formatting.
  • ControlValueID (4): Supports simple value monitoring (like a checkbox state).
  • PositionLinkID (3): Supports coordinate or index-based changes.

ObserverAdded: The Handshake

If CanObserve returns True, the engine proceeds to add the observer and then calls ObserverAdded. This is the component's opportunity to perform a "handshake"—setting up specific event handlers or internal state changes that should only exist when an observer is active.

The Identification System: TObserverMapping

How does the component know which logic to apply? Delphi uses the TObserverMapping class to manage these IDs. It provides a standardized way to request specific "behaviors" from a component without needing to know the component's concrete class type.

// Typical usage in a component override
function TMyCustomControl.CanObserve(const ID: Integer): Boolean;
begin
  Result := (ID = TObserverMapping.EditLinkID) or (ID = TObserverMapping.ControlValueID);
end;

By splitting the logic this way, Delphi keeps the core TObservers engine generic and fast, while allowing every individual component to define its own unique rules for interaction.


In Part 4: We look at a concrete example of this in action within TCustomEdit, and how it uses the ObserverToggle event to save and restore UI state.