Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2012-08

Recent added support for Observer pattern in FCL

14 messages · 4 people ·

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

Luiz Americo Pereira Camara

Back in 2010, in the thread about observer support in fcl, i asked if the actions would be customizable so i could, e.g., notify/observe if a child was added or removed in a tree structure. The answer was yes.

Looking at the recent added support, it limit the actions (called operations) to 5 types

TFPObservedOperation = (ooChanged,ooFree,ooAddItem,ooDeleteItem,ooCustom);

I ask to change to a mechanism that could allow customizable operations (more than one)

I'm asking that because i have implemented a similar mechanism that support operations like Load, ChildAdd, ChildDelete, ChildChange. I believe that more developers are in the same situation,i.e., using observer pattern to an action not handled by those 5 types. It would be helpful to use the native implementation otherwise i will have to keep(re-implement) the observer support over TPersistent.

BTW:
The names of the TFPObservedOperation are not consistent: ooChanged is in preterit while others not
ooChanged does not has Item suffix

it should be
ooChange,ooAdd,ooDelete
or
ooChanged,ooAdded,ooDeleted
or
ooChangeItem,ooAddItem,ooDeleteItem
or
ooChangedItem,ooAddedItem,ooDeletedItem

The changes: http://svn.freepascal.org/cgi-bin/viewvc.cgi?view=rev&revision=22257

See the cited email:

Michael Van Canneyt wrote 9 lines
On Thu, 20 May 2010, Florian Klaempfl wrote: > I've no opinion if it's usefull to add or not, I use TPersistent+ too > little but my concern is: if I create an observer for an instance, I'd > expect to get notified about every change to the instance. But I cannot > see how this will be achieved, no existing class is prepared for this? Not every change. What you get notified about is defined by the class that uses the observable interface. TStrings will notify you of additions/removals, and TList/TObjectList as well.

It will send some kind of info (like index or address of the object) to know what and where the item was added or removed?

Yes.

Can the same Obsever observe (:-D) more than of one type of object? e.g. i want to observe a object list for adding or removals, but also want to know if one of the child objects changed.

Yes. You can observe any object that will implement the IFPObservable interface.

Michael.

Reply by email

Unknown author
to Luiz Americo Pereira Camara
Luiz Americo Pereira Camara wrote 11 lines
Back in 2010, in the thread about observer support in fcl, i asked if the actions would be customizable so i could, e.g., notify/observe if a child was added or removed in a tree structure. The answer was yes. Looking at the recent added support, it limit the actions (called operations) to 5 types TFPObservedOperation = (ooChanged,ooFree,ooAddItem,ooDeleteItem,ooCustom); I ask to change to a mechanism that could allow customizable operations (more than one)

That is why you have ooCustom.

Show quoted text 8 lines
I'm asking that because i have implemented a similar mechanism that support operations like Load, ChildAdd, ChildDelete, ChildChange. I believe that more developers are in the same situation,i.e., using observer pattern to an action not handled by those 5 types. It would be helpful to use the native implementation otherwise i will have to keep(re-implement) the observer support over TPersistent.

That's why you have ooCustom and Data at your disposal.

I imagined 3 scenarios for this:

The first:

Procedure TmyObject.SomethingChanged;
Var
   Rec : TMyOperationInfo; // Contains custom information about your operation.
begin
   Rec.XYZ:=MoreInfo; // Set up as needed.
   FPONotifyObservers(Self,ooCustom,@Rec);
end;

Or, you can do

Procedure TmyObject.SomethingChanged;
Var
   Obj : TMyOperationInfo; // Contains custom information about your operation.
begin
   Obj:=TMyOperationInfo.Create(MyParams);
   try
     FPONotifyObservers(Self,ooCustom,Pointer(Obj));
   finally
     Obj.free;
   end;
end;
Procedure TmyObject.SomeThingChanged(APropName : String);
begin
   FPONotifyObservers(Self,ooCustom,GetPropInfo(Self,APropName));
end;

But I think the additional Create/Free may be heavy. That is why I decided on a data pointer, not an object. It allows to use records as well as objects.

The last scenario was for property change notifications:

Procedure TmyObject.SomeThingChanged(Const APropName : String);
begin
   FPONotifyObservers(Self,ooCustom,GetPropInfo(Self,APropName));
end;

This could be called from a property setter.

There are many ways to implement an observer pattern. It is always a balance between lightweight and extensibility. I believe the implemented mechanism is both: it does not require additional objects (and thus heap operations) but does allow using them.

Show quoted text 5 lines
BTW: The names of the TFPObservedOperation are not consistent: ooChanged is in preterit while others not ooChanged does not has Item suffix

It does not need one. The sender has changed, not an item in a list. The 'Item'-s are for when sender is a list, and an item is added to the list. (TList, TStringList, TCollection)

That's a matter of documentation.

Michael.

Reply by email

Graeme Geldenhuys
to Luiz Americo Pereira Camara: Back in 2010, in the thread about observer support in fcl, i asked if the actions would…
Luiz Americo Pereira Camara wrote
Back in 2010, in the thread about observer support in fcl, i asked if the actions would be customizable so i could, e.g., notify/observe if a child was added or removed in a tree structure. The answer was yes.

I've emailed Michael 2-3 weeks ago about a previous mailing list discussion about Observer support too - which I concluded as will be accepted, Michael just needs time to commit his changes. Adding support for Observer into base RTL classes (not FCL). Any extra resources will only be used when the Observer functionality is actually requested, by the developer, it can't break any existing code because no such functionality existed before etc. Michael said it was "again under discussion" behind closed doors, but he will notify me of the outcome.

Myself, and I know Michael van Canneyt too, would really like Observer support in base RTL classes. This is immensely useful, and means other downstream features like a total replacement for db-aware components (which Michael also started on already) could be implement in projects like Lazarus, fpGUI etc. I can already use the latter to some degree, with the help of tiOPF, but sometimes I want other observer functionality in my apps too, or don't want to depend on tiOPF, and having to created descendants of base RTL classes defeats the whole point.

The names of the TFPObservedOperation are not consistent:

A valid point, which I actually never noticed before.

Regards,
- Graeme -

Reply by email

Unknown author
to Graeme Geldenhuys
Graeme Geldenhuys wrote 13 lines
On 28/08/12 02:48, Luiz Americo Pereira Camara wrote: > Back in 2010, in the thread about observer support in fcl, i asked if > the actions would be customizable so i could, e.g., notify/observe if a > child was added or removed in a tree structure. The answer was yes. I've emailed Michael 2-3 weeks ago about a previous mailing list discussion about Observer support too - which I concluded as will be accepted, Michael just needs time to commit his changes. Adding support for Observer into base RTL classes (not FCL). Any extra resources will only be used when the Observer functionality is actually requested, by the developer, it can't break any existing code because no such functionality existed before etc. Michael said it was "again under discussion" behind closed doors, but he will notify me of the outcome.

I think you didn't read the original poster's mail subject line 100% carefully :-)

The support has been committed. I didn't get around to notifying you yet.

Michael.

Reply by email

Graeme Geldenhuys
to Unknown author: That is why you have ooCustom. That's why you have ooCustom and Data at your disposal. I…
Show quoted text 12 lines
The first: Procedure TmyObject.SomethingChanged; Var Rec : TMyOperationInfo; // Contains custom information about your operation. begin Rec.XYZ:=MoreInfo; // Set up as needed. FPONotifyObservers(Self,ooCustom,@Rec); end;

Correct, and this is how I use it in tiOPF too — which has a similar observer implementation.

It does not need one. The sender has changed, not an item in a list. The 'Item'-s are for when sender is a list, and an item is added to the list.

Ah yes, I understand your thinking now. Scrap my earlier comment on this point. :)

Regards,
- Graeme -

Reply by email

Graeme Geldenhuys
to Unknown author: I think you didn't read the original poster's mail subject line 100% carefully :-) The…
I think you didn't read the original poster's mail subject line 100% carefully :-)

My apologies, I thought he was commenting on the FPObserver support added as a unit to FCL, not RTL. The subject line quoting a old post or something.

The support has been committed. I didn't get around to notifying you yet.

I just pulled a Trunk update and can see the changes now. Thank you very much for this contribution, it is going to come in very handy!

Regards,
- Graeme -

Reply by email

Luiz Americo Pereira Camara
to Unknown author: That is why you have ooCustom. That's why you have ooCustom and Data at your disposal. I…
Show quoted text 20 lines
On Mon, 27 Aug 2012, Luiz Americo Pereira Camara wrote: > I ask to change to a mechanism that could allow customizable > operations (more than one) That is why you have ooCustom. > > > I'm asking that because i have implemented a similar mechanism that > support operations like Load, ChildAdd, ChildDelete, ChildChange. I > believe that more developers are in the same situation,i.e., using > observer pattern to an action not handled by those 5 types. It would > be helpful to use the native implementation otherwise i will have to > keep(re-implement) the observer support over TPersistent. That's why you have ooCustom and Data at your disposal.

ooCustom is already used in TCollection if i remember correct. If i use it for a TCollection descendant that uses ooCustom with a record pointer i would get a conflict and a possible crash.

Why not let TFPObservedOperation as integer and define constants.

It could define a constant like ooUser = 32
Below ooUser is reserved for future default constants Above ooUser could be added custom messages
This is how LCL messages works

Its both light and extensible

Show quoted text 11 lines
> BTW: > The names of the TFPObservedOperation are not consistent: > ooChanged is in preterit while others not > > ooChanged does not has Item suffix It does not need one. The sender has changed, not an item in a list. The 'Item'-s are for when sender is a list, and an item is added to the list. (TList, TStringList, TCollection)

I did the remark because in TCollection is fired because a a change in a Item. But is fine.

Any way the preterit question is still valid Changed - Added - Deleted or Change - Add - Delete

Luiz

Reply by email

Unknown author
to Luiz Americo Pereira Camara
Luiz Americo Pereira Camara wrote 23 lines
Em 28/8/2012 04:50, escreveu: > > > On Mon, 27 Aug 2012, Luiz Americo Pereira Camara wrote: > >> I ask to change to a mechanism that could allow customizable operations >> (more than one) > > That is why you have ooCustom. > >> >> >> I'm asking that because i have implemented a similar mechanism that >> support operations like Load, ChildAdd, ChildDelete, ChildChange. I >> believe that more developers are in the same situation,i.e., using >> observer pattern to an action not handled by those 5 types. It would be >> helpful to use the native implementation otherwise i will have to >> keep(re-implement) the observer support over TPersistent. > > That's why you have ooCustom and Data at your disposal. > ooCustom is already used in TCollection if i remember correct.

In TList and TCollection. I will change it to ooDeleteItem; The effect is the same. Extract and Delete are the same operation at the pointer level, after all.

If i use it for a TCollection descendant that uses ooCustom with a record pointer i would get a conflict and a possible crash.

A useful observation, and I will take it into account.

Show quoted text 7 lines
Why not let TFPObservedOperation as integer and define constants. It could define a constant like ooUser = 32 Below ooUser is reserved for future default constants Above ooUser could be added custom messages This is how LCL messages works

Exactly because it is not meant as a general messaging interface, and I want the interface to reflect this.

There is a clear intended use case: Mainly notification of changes.

In this sense, it is a unification and extension of the TStrings.Changed,
TCollection.Notify and
TList.Notify
which already were defined by Delphi (and which, for some strange reason, have different interfaces).

The enumerated reflects the intended use cases:
Change notification, and for list-type objects, insertion and deletion of items.

If you want to go outside that, then this is possible too: you can use ooCustom and Data.

I will make sure it is not used in standard notifications. (see the change above)

Using the outlined use-cases I gave you can still define an extension with an integer and put that in the object/record to use on ooCustom.

If someone has an idea about a general-purpose notification type then that can of course be discussed.

For example, on my 'doubtful' list I had "ooPropertyChange", to be used in combination with the GetPropInfo() and a call in TPersistent:

FPOPropertyChanged(Const APropName : String);

I'm still not 100 % sure what to do with that.

As for the preterit in ooChanged, I will look at it too.

Michael.

Reply by email

Graeme Geldenhuys
to Unknown author
Show quoted text 5 lines
For example, on my 'doubtful' list I had "ooPropertyChange", to be used in combination with the GetPropInfo() and a call in TPersistent: FPOPropertyChanged(Const APropName : String);

This could work, and I can see use-cases where this could be useful. I'm not sure how much overhead it will add though, but probably very small if done correctly. How would streaming affect this? eg: populating objects from a .lfm file. I don't use such streaming, but thought I need to mention it. For more accurate information, I guess the best would be to do a reference implementation, create a test case, and decide from there.

Regards,
- Graeme -

Reply by email

Unknown author
to Graeme Geldenhuys
Graeme Geldenhuys wrote 14 lines
On 28/08/12 13:15, wrote: > > For example, on my 'doubtful' list I had "ooPropertyChange", to be used > in combination with the GetPropInfo() and a call in TPersistent: > > FPOPropertyChanged(Const APropName : String); This could work, and I can see use-cases where this could be useful. I'm not sure how much overhead it will add though, but probably very small if done correctly. How would streaming affect this? eg: populating objects from a .lfm file. I don't use such streaming, but thought I need to mention it. For more accurate information, I guess the best would be to do a reference implementation, create a test case, and decide from there.

Well, I can add the call, but I would not call it automatically. It is up to the implementor of a class to call the FPOPropertyChanged from a setter.

I imagine that this could be protected with a

if not (csLoading in ComponentState) then
   FPOPropertyChanged(APropName);

Michael.

Reply by email

Luiz Americo Pereira Camara
to Unknown author: In TList and TCollection. I will change it to ooDeleteItem; The effect is the same.…
Show quoted text 16 lines
On Tue, 28 Aug 2012, Luiz Americo Pereira Camara wrote: > > Why not let TFPObservedOperation as integer and define constants. > > It could define a constant like ooUser = 32 > Below ooUser is reserved for future default constants > Above ooUser could be added custom messages > This is how LCL messages works Exactly because it is not meant as a general messaging interface, and I want the interface to reflect this. There is a clear intended use case: Mainly notification of changes.

This is the purpose i intend to use (replace my implementation). Basically is a tree structure that needs to be observed. To avoid performance problems like having to parse all the tree when a change occurs i defined actions when a Child is added, deleted or inserted.

To be clear what i would need to do with the current approach:

const
   ChildAdded = 1;
type
   TMyNotifyInfo = record
      NType: Integer;
      NData: Pointer;
   end;
   PMyNotifyInfo = ^TMyNotifyInfo;
//notify
procedure TMyObserved.Changed;
var
   R: TMyNotifyInfo;
begin
   R.NType := ChildAdded;
   R.NData := ChildItemObject;
   FPONotifyObservers(Self,ooCustom,@R);
end;
//observe
procedure TMyObserver.FPOObservedChanged(ASender : TObject; Operation :
TFPObservedOperation; Data : Pointer);
var
   R: PMyNotifyInfo;
begin
   if Operation = ooCustom then
   begin
      R := PMyNotifyInfo(Data);
      if R^.Ntype = ChildAdded then
         DoChildAdd(R^.NData);
   end;
end;

compare with my proposed solution of defining the type as integer:

const
   ooChildAdded = ooUser + 1;
//notify
procedure TMyObserved.Changed;
begin
   FPONotifyObservers(Self, ooChildAdded, ChildItemObject);
end;
//observe
procedure TMyObserver.FPOObservedChanged(ASender : TObject; Operation :
TFPObservedOperation; Data : Pointer);
begin
   if Operation = ooChildAdded then
   begin
         DoChildAdd(Data);
   end;
end;

Simpler, less code, less memory use, extensible.

All that with just changing the TFPObservedOperation to a Integer or word type

IMO such feature at a low level class should be usable in the most use cases, otherwise it will have a half observer support.

My proposition does not increase complexity of the code, nor any type of overhead with the above gains.

Can you point the drawbacks?

Luiz

Reply by email

Unknown author
to Luiz Americo Pereira Camara
Luiz Americo Pereira Camara wrote 22 lines
Em 28/8/2012 09:15, escreveu: > > > On Tue, 28 Aug 2012, Luiz Americo Pereira Camara wrote: > >> >> Why not let TFPObservedOperation as integer and define constants. >> >> It could define a constant like ooUser = 32 >> Below ooUser is reserved for future default constants >> Above ooUser could be added custom messages >> This is how LCL messages works > > Exactly because it is not meant as a general messaging interface, and I > want the interface to reflect this. > > There is a clear intended use case: Mainly notification of changes. This is the purpose i intend to use (replace my implementation). Basically is a tree structure that needs to be observed. To avoid performance problems like having to parse all the tree when a change occurs i defined actions when a Child is added, deleted or inserted.

Exactly. You should use ooAddItem, ooDeleteItem, and pass the treenode in Data.

To be clear what i would need to do with the current approach:

Your example is not correct. I don't see why you would not use ooItemAdded.

You observe a tree. Your observer code knows what it means if the tree sends a ooItemAdd or ooItemDelete. There is not even the need for a custom type here.

Do not forget that the implementor of some control decides (and documents) what notifications he/she will send. The 4 standard ones will probably cover everything. The observers need to know what they observe, and know what to do for each operation.

If you make the operation an integer, an observer will never know if it has covered all operations. With an enumerated type, the observer can implement the 5 enumerated values, and know that all operations have been covered.

We can agree on the exact number of operations that we define, but I will not implement a system with an open number of operations.

If you think that each and every possibly imaginable operation needs its own type of message, then you are IMHO on the wrong track. My original implementation didn't even have ooCustom, and I could observe all controls in the LCL with just the 4 messages ooChange,ooFree,ooItemAdd and ooItemDelete; That included observing a tree.

Today or tomorrow I will commit the fpObserver unit, which contains a basic mediator factory which I used to implement just that.

Michael.

Reply by email

Luiz Americo Pereira Camara
to Unknown author
Show quoted text 35 lines
On Tue, 28 Aug 2012, Luiz Americo Pereira Camara wrote: > Em 28/8/2012 09:15, escreveu: >> >> >> On Tue, 28 Aug 2012, Luiz Americo Pereira Camara wrote: >> >>> >>> Why not let TFPObservedOperation as integer and define constants. >>> >>> It could define a constant like ooUser = 32 >>> Below ooUser is reserved for future default constants >>> Above ooUser could be added custom messages >>> This is how LCL messages works >> >> Exactly because it is not meant as a general messaging interface, >> and I want the interface to reflect this. >> >> There is a clear intended use case: Mainly notification of changes. > > This is the purpose i intend to use (replace my implementation). > Basically is a tree structure that needs to be observed. To avoid > performance problems like having to parse all the tree when a change > occurs i defined actions when a Child is added, deleted or inserted. Exactly. You should use ooAddItem, ooDeleteItem, and pass the treenode in Data. > > To be clear what i would need to do with the current approach: Your example is not correct. I don't see why you would not use ooItemAdded.

This was just the first thing that comes in mind.

I have a simple interface to observing a tree. Looking more carefully i really can use ooItem* for children. I also defined a "Change" notification for the object itself plus another for the child/item but i could differentiate using data as nil. The other missing one is to Load (lazy loading)

We can agree on the exact number of operations that we define, but I will not implement a system with an open number of operations.

I will do some tests and inform if there's a generic enough case to be included as a default notification

Thanks for the explanations

Luiz

Reply by email

Hans-Peter Diettrich
to Luiz Americo Pereira Camara: This is the purpose i intend to use (replace my implementation). Basically is a tree…
This is the purpose i intend to use (replace my implementation). Basically is a tree structure that needs to be observed. To avoid performance problems like having to parse all the tree when a change occurs i defined actions when a Child is added, deleted or inserted.

Why not create dedicated TObservedTree and TTreeObserver classes, which work together?

This would allow for whatever kind of notifications required in a certain tree model, without any impact on the basic tree class.

DoDi

Reply by email