On 19/05/2010, Marco van de Voort wrote:
>
> First: I don't see an urgent need for such draconic measures.

So Michael having to wait 10 years to see it implemented is called
"urgent" in your eye?  :)


>  Second: I don't see a good solution, exactly because the whole framework was
>  not designed for this.

No need for a whole framework designed. Just adding some extra
functionality. Clearly you need a code example to understand it. See
the code example below.


>  E.g. an Observer pattern typically uses interfaces, and interfaces are not
>  designed into the framework.

Hell no! Observer has nothing to do with interfaces! The Observer is
implemented in tiOPF without interfaces. Using interfaces just makes
it easier to inject Observable behaviour - but injecting behaviour is
a feature of interfaces in general (not specific to Observer). The
other benefit of interfaces (now that we have interface delegation) is
that you only need to implement the Observer once and bolt that
implementation onto other classes. Saving you lots of duplicated code
and time.

Attached is a stripped down version of Observer implemented as a separate unit.

And here is how you can add Observer functionality to a class using
interfaces (purely to reduce code). This is what Michael and I are
proposing we do to base classes like TStrings or even TPersistent etc.

(note the final implementation my not look 100% like this - this is
just a quick example):


{$interfaces corba}

  TMyClass = class(TList, IFPObserved)
  private
    FObservedHook: TObservedHook;
    property  ObservedHook: TObservedHook read FObservedHook
implements IFPObserved; // are will always access it via a interface
so it can stay private
  public
     function Add(....): Integer; override;
  end;

function TMyClass.Add(....)
begin
  inherited Add(...)
  if Assigned(ObservedHook) then
    ObservedHook.NotifyObservers(self, ooChanged);
end;


That's it! Now TMyClass can be observed. No huge amounts of  code or
"draconic measures" as you phrased it.

You can now add observers of that class as follows in your program:

  TMakeMeObserveSomething = class(TObject, IFPObserver)
  private
     procedure ObservedChanged(ASender: TObject;
           Operation: TObservedOperation);
  public
     procedure DoItNow;
  end;

procedure TMakeMeObserveSomething.DoItNow;
var
  intf: IFPObserved;
begin
  if MyClass.GetInterface(IFPObserved, intf) then
    intf.AttachObserver(self);
end;


In tiOPF's implementation of Observer I optimized the code that extra
resources are only used when you actually use the observable
functionality by adding an observer the for the first time. I also
added extra optimization so that when you add many items for example
to a list that it works similar to BeginUpdate; .... EndUpdate; so
that observers are only notified once when a long running action is
completed or after multiple items was added to a list.  Similar
optimization could be added the what Michael is proposing (if he
hasn't implemented this yet).


-- 
Regards,
  - Graeme -


_______________________________________________
fpGUI - a cross-platform Free Pascal GUI toolkit
http://opensoft.homeip.net/fpgui/
unit fpg_observer;

{$ifdef FPC}
	{$mode objfpc}{$h+}
	{$interfaces corba}
{$endif}

interface

uses
	Classes, SysUtils; 
	
const
  SGUIDObserved = '{663C603C-3F3C-4CC5-823C-AC8079F979E5}';
  SGUIDObserver = '{BC7376EA-199C-4C2A-8684-F4805F0691CA}';

  GUIDObserved : TGUID = SGUIDObserved;
  GUIDObserver : TGUID = SGUIDObserver;

type
  // Notification operations :
  // Observer has changed, is freed, item added to/deleted from list, custom event.
  TObservedOperation = (ooChanged,ooFree,ooAddItem,ooDeleteItem,ooCustom);

  IFPObserved = interface[SGUIDObserved]
    // attach a new observer
    procedure AttachObserver(AObserver : TObject);
    // Detach an observer
    procedure DetachObserver(AObserver : TObject);
    // Notify all observers of a change.
    procedure NotifyObservers(ASender : TObject; AOperation : TObservedOperation);
  end;

  IFPObserver = Interface  [SGUIDObserver]
    // Called by observed when observers are notified.
    procedure ObservedChanged(ASender: TObject; Operation: TObservedOperation);
  end;

  { TObservedHook }

  { Use this as an encapsulated mechanism for implementing TFPObserved }

  TObservedHook = class(TObject, IFPObserved)
  Protected
    FObservers : TFPList;
    FSender : TObject;
  Public
    // ASender will be the default sender.
    constructor CreateSender(ASender : TObject);
    destructor Destroy; override;
    procedure AttachObserver(AObserver : TObject);
    procedure DetachObserver(AObserver : TObject);
    procedure Changed;
    procedure AddItem(AItem : TObject);
    procedure DeleteItem(AItem : TObject);
    procedure CustomNotify;
    procedure NotifyObservers(ASender : TObject; AOperation : TObservedOperation);
    property Sender : TObject Read FSender;
  end;

  EObserver = Class(Exception);

implementation

resourcestring
  SErrNotObserver = 'Instance of class %s is not an observer.';
  SErrInvalidPropertyName = '%s is not a valid published property of class %s';
  SErrObjectCannotBeObserved = 'Cannot observe an instance of class %d';
  sErrInvalidFieldName      = 'No fieldname specified for column %d';
  sErrInvalidAlignmentChar  = 'Invalid alignment character "%s" specified for column %d';
  sErrInvalidWidthSpecifier = 'Invalid with "%s" specified for column %d';
  sErrNotListObject         = '%s is not a TObservedObjectList';
  sErrCompositeNeedsList    = '%s needs a TObservedObjectList class but is registered with %s';
  SErrActive                = 'Operation not allowed while the mediator is active';
  SErrNoGuiFieldName        = 'no gui fieldname set';
  SErrNoSubjectFieldName    = 'no subject fieldname set';

{ TObservedHook }

constructor TObservedHook.CreateSender(ASender: TObject);
begin
  FSender:=ASender;
  If FSender=Nil then
    FSender:=Self;
end;

destructor TObservedHook.Destroy;
begin
  If Assigned(FObservers) then
    begin
    NotifyObservers(FSender,ooFree);
    FreeAndNil(FObservers);
    end;
  inherited Destroy;
end;

procedure TObservedHook.AttachObserver(AObserver: TObject);
var
  I : IFPObserver;
begin
  If Not AObserver.GetInterface(SGUIDObserver,I) then
    Raise EObserver.CreateFmt(SErrNotObserver,[AObserver.ClassName]);
  If not Assigned(FObservers) then
    FObservers:=TFPList.Create;
  FObservers.Add(AObserver);
end;

procedure TObservedHook.DetachObserver(AObserver: TObject);
var
  I : IFPObserver;
begin
  If Not AObserver.GetInterface(SGUIDObserver,I) then
    Raise EObserver.CreateFmt(SErrNotObserver,[AObserver.ClassName]);
  If Assigned(FObservers) then
    begin
    FObservers.Remove(AObserver);
    If (FObservers.Count=0) then
      FreeAndNil(FObservers);
    end;
end;

procedure TObservedHook.Changed;
begin
  NotifyObservers(Sender,ooChanged)
end;

procedure TObservedHook.AddItem(AItem: TObject);
begin
  NotifyObservers(AItem,ooAddItem);
end;

procedure TObservedHook.DeleteItem(AItem: TObject);
begin
  NotifyObservers(AItem,ooDeleteItem);
end;

procedure TObservedHook.CustomNotify;
begin
  NotifyObservers(Sender,ooCustom);
end;

procedure TObservedHook.NotifyObservers(ASender: TObject;  AOperation: TObservedOperation);
var
  O : TObject;
  I : Integer;
  Obs : IFPObserver;
begin
  If Assigned(FObservers) then
    For I:=FObservers.Count-1 downto 0 do
      begin
      O:=TObject(FObservers[i]);
      If O.GetInterface(SGUIDObserver,Obs) then
        Obs.ObservedChanged(ASender,AOperation);
      end;
end;

end.
_______________________________________________
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-devel

Reply via email to