Recent

Author Topic: Observers  (Read 4738 times)

Weitentaaal

  • Hero Member
  • *****
  • Posts: 503
  • Weitental is a very beautiful garbage depot.
Observers
« on: October 25, 2021, 01:57:41 pm »
Hello,

So i wanted to ask if there is a Simple Example Somewhere on how to use Observers. I know how it works in theory but not how in practice.

Problem Simplified (its more komplex with much more Components, thats the reason why i wanted to decapsulate my GUI from my Data/Calculations and Notify both classes about Changes via Observers):
I have 2 classes: A GUI form with 3 TEdit's, and a DataClass which stores the values of these 3 Edit's, in e.g. Val1, Val2 and Val3.
If I change Edit1 or Edit2, I want to notify my data class. This class should then update the changed fields. After that the value of Edit1 and Edit2 are added together and the result is written to Val3. Since Val3 has now changed and therefore no longer has the same value as Edit3 from the GUI, this field should now also be updated by the observer.

I hope you understood what I am trying to achieve.

My Programm just gets bigger and bigger so i wanted to implement an MVC pattern, buts that a bit too complex jet for me so i just wanted to start with encapsulate the GUI from Calculations/Data and Communicate with Observers.
For Example an Observer for every TEdit i have. Currently using onChange events but my code gets unreadable. I reuse those Events like this: Combo1Change(Nil), and have like everything mixed in this whole Class.

I would be very happy if someone can help me here. :)
Lazarus: 2.0.12 x86_64-win64-win32/win64
Compiler Version: 3.2.2

nummer8

  • Full Member
  • ***
  • Posts: 108
Re: Observers
« Reply #1 on: October 25, 2021, 02:20:29 pm »
Michael wrote several articles.

You can find them at the address below.
The article about the cdcover implements the observer pattern.
source-code is available

https://www.freepascal.org/~michael/articles/
https://www.freepascal.org/~michael/articles/cdcover/cdcover.pdf


white_zombie

  • New Member
  • *
  • Posts: 18
Re: Observers
« Reply #2 on: October 25, 2021, 03:02:31 pm »
I don't remember where exactly I got this example (I think it was in the FPC mailing list), but maybe it can help you to understand the observer pattern.

Code: Pascal  [Select][+][-]
  1. { This demo is very basic, but shows the Observer support in the RTL }
  2. program fpobserver_demo;
  3.  
  4. {$mode objfpc}{$h+}
  5. {$ifdef mswindows}{$apptype console}{$endif}
  6.  
  7. uses
  8.  Classes, SysUtils, typinfo;
  9.  
  10. type
  11.   TMyObserver = class(TObject, IFPObserver)
  12.   private
  13.     procedure FPOObservedChanged(ASender : TObject; Operation : TFPObservedOperation; Data : Pointer);
  14.   end;
  15.  
  16. { TMyObserver }
  17.  
  18. procedure TMyObserver.FPOObservedChanged(ASender: TObject;
  19.                Operation: TFPObservedOperation; Data: Pointer);
  20.  
  21.   function OperationToString(AOperation: TFPObservedOperation): string;
  22.   begin
  23.     result := GetEnumName(TypeInfo(TFPObservedOperation),
  24.                          Ord(AOperation));
  25.   end;
  26. var
  27.   intf: IFPObserved;
  28. begin
  29.   if Operation = ooFree then
  30.   begin
  31.     writeln('[ooFree] detected so we should detach ourselves');
  32.     if Supports(ASender, IFPObserved, intf) then
  33.       intf.FPODetachObserver(self);
  34.   end
  35.   else
  36.   begin
  37.     writeln(ASender.ClassName + ' has changed ['+
  38.       OperationToString(Operation) + ']');
  39.   end;
  40. end;
  41.  
  42. var
  43.   sl: TStringList;
  44.   observer: TMyObserver;
  45.   intf: IFPObserved;
  46. begin
  47.   { This stringlist will be the subject (observed) }
  48.   sl := TStringList.Create;
  49.   { this instance will be the observer - notified when StringList changes }
  50.   observer := TMyObserver.Create;
  51.  
  52.   { attach observer }  
  53.   if Supports(sl, IFPObserved, intf) then
  54.   begin
  55.     intf.FPOAttachObserver(observer);
  56.   end;
  57.  
  58.   { Do something to the stringlist }
  59.   sl.Add('Item one');
  60.   sl.Add('Item two');
  61.   sl.Delete(0);
  62.  
  63.   { Clean-up code - also shows ooFree operation }
  64.   sl.Free;
  65.   observer.Free;
  66. end.
  67.  
  68.  

Weitentaaal

  • Hero Member
  • *****
  • Posts: 503
  • Weitental is a very beautiful garbage depot.
Re: Observers
« Reply #3 on: October 25, 2021, 03:26:00 pm »
Thanks :) if there are more Examples or Explanations please let me know. i realy want to know what i am doing !
Lazarus: 2.0.12 x86_64-win64-win32/win64
Compiler Version: 3.2.2

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Observers
« Reply #4 on: October 25, 2021, 04:35:52 pm »
Here is another very simple example using TStringList (which already implements IFPObserved) and TEdit (which does not implement IFPObserved, so you have to add it).
The example is based on your skeleton of two edits whose Texts the observing control combines.

jamie

  • Hero Member
  • *****
  • Posts: 6091
Re: Observers
« Reply #5 on: October 25, 2021, 05:00:00 pm »
using posted user messages to the form that houses the receiving components can also work.

You only need to have the Handle of the form that has the components in it so that you can process the incoming messages.
The only true wisdom is knowing you know nothing

BeniBela

  • Hero Member
  • *****
  • Posts: 905
    • homepage
Re: Observers
« Reply #6 on: October 25, 2021, 05:53:59 pm »
Protoss makes them in the Robotics Facility. Then just move them with all the other units  :D

Weitentaaal

  • Hero Member
  • *****
  • Posts: 503
  • Weitental is a very beautiful garbage depot.
Re: Observers
« Reply #7 on: October 26, 2021, 01:14:11 pm »
Here is another very simple example using TStringList (which already implements IFPObserved) and TEdit (which does not implement IFPObserved, so you have to add it).
The example is based on your skeleton of two edits whose Texts the observing control combines.

This was very usefull Thanks ! Could i Change it like that:

From

"TEditObserver = class(TEdit, IFPObserver)"

to

"TMyClassObserver = class(TComponent, IFPObserver)"

and then use this Class to observe all my Visual Components on my Form ? :)

Protoss makes them in the Robotics Facility. Then just move them with all the other units  :D

Sorry couldn't quiet follow  %)

using posted user messages to the form that houses the receiving components can also work.

You only need to have the Handle of the form that has the components in it so that you can process the incoming messages.


So i could write a Procedure which is receiving the Sender(TComponent) and then use it in the other class ?did i understand that correct ?  :-[
Lazarus: 2.0.12 x86_64-win64-win32/win64
Compiler Version: 3.2.2

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Observers
« Reply #8 on: October 26, 2021, 10:36:22 pm »
Could i Change it like that:

From

"TEditObserver = class(TEdit, IFPObserver)"

to

"TMyClassObserver = class(TComponent, IFPObserver)"

and then use this Class to observe all my Visual Components on my Form ? :)
Of course you can.
The only drawback is the limited number of useful TComponent properties (Owner, Name, Tag and a few others).
 But better to name it TMyComponentObserver.
I think you would find an alternative more useful:
Code: Pascal  [Select][+][-]
  1. TControlObserver = class(TControl, IFPObserver);
« Last Edit: October 26, 2021, 10:39:27 pm by howardpc »

Weitentaaal

  • Hero Member
  • *****
  • Posts: 503
  • Weitental is a very beautiful garbage depot.
Re: Observers
« Reply #9 on: October 27, 2021, 07:31:58 am »
Could i Change it like that:

From

"TEditObserver = class(TEdit, IFPObserver)"

to

"TMyClassObserver = class(TComponent, IFPObserver)"

and then use this Class to observe all my Visual Components on my Form ? :)
Of course you can.
The only drawback is the limited number of useful TComponent properties (Owner, Name, Tag and a few others).
 But better to name it TMyComponentObserver.
I think you would find an alternative more useful:
Code: Pascal  [Select][+][-]
  1. TControlObserver = class(TControl, IFPObserver);

Thank you :)
Lazarus: 2.0.12 x86_64-win64-win32/win64
Compiler Version: 3.2.2

Weitentaaal

  • Hero Member
  • *****
  • Posts: 503
  • Weitental is a very beautiful garbage depot.
Re: Observers
« Reply #10 on: November 02, 2021, 04:46:30 pm »
Hello again,

Could you guys please have a Look at my Example:

Code: Pascal  [Select][+][-]
  1. unit WGK2Example;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.    Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  9.  
  10. type
  11.    TObserver = class(TInterfacedObject, IFPObserver)
  12.    protected
  13.      fActiveObject: TObject;
  14.    public
  15.      constructor Create(anActiveobject: TObject);
  16.      destructor Destroy; override;
  17.      Procedure FPOObservedChanged(aSender: TObject; Operation: TFPObservedOperation; Data: Pointer); virtual;
  18.      property ActiveObject: TObject read fActiveObject write fActiveObject;
  19.    end;
  20.  
  21.    TObserved = class(TInterfacedObject, IFPObserved)
  22.    private
  23.      fObservers: TFPList;
  24.    protected
  25.      fSubject: TObject;
  26.    public
  27.      constructor Create(aSubject: TObject); //The Form i want to monitor
  28.      destructor Destroy; override;
  29.      procedure FPOAttachObserver(AObserver: TObject);
  30.      procedure FPODetachObserver(AOBserver: TObject);
  31.      procedure FPONotifyObservers(ASender: TObject; AOperation: TFPObservedOperation; Data: Pointer);
  32.  
  33.      property Subject: TForm read fSubject write fSubject;
  34.      property Observers: TFPList read fObservers write fObservers;
  35.    end;
  36.  
  37.    { TForm2 }
  38.  
  39.    TForm2 = class(TForm)
  40.       Edit1: TEdit;
  41.       Edit2: TEdit;
  42.       Edit3: TEdit;
  43.       Edit4: TEdit;
  44.       procedure EditChange(Sender: TObject);
  45.       procedure FormShow(Sender: TObject);
  46.    private
  47.  
  48.    public
  49.      Form2Observer: TObserver;
  50.      Form2Observed: TObserved;
  51.    end;
  52.  
  53. var
  54.    Form2: TForm2;
  55.  
  56. implementation
  57.  
  58. {$R *.lfm}
  59.  
  60. { TForm2 }
  61.  
  62. procedure TForm2.FormShow(Sender: TObject);
  63. begin
  64.    Form2Observed:= TObserved.Create(Form2);
  65.  
  66.    Form2Observed.FPOAttachObserver(TObserver.Create(Edit1));
  67.    Form2Observed.FPOAttachObserver(TObserver.Create(Edit2));
  68.    Form2Observed.FPOAttachObserver(TObserver.Create(Edit3));
  69.    Form2Observed.FPOAttachObserver(TObserver.Create(Edit4));
  70. end;
  71.  
  72. procedure TForm2.EditChange(Sender: TObject);
  73. begin
  74.    if Sender.Equals(Edit1) then begin
  75.       //what do i do Here ??
  76.       Form2Observed.FPONotifyObservers({What should be here ?} , ooChange, Pointer(Edit1.Text));
  77.    end
  78.    else if Sender.Equals(Edit2) then
  79.       Form2Observed.FPONotifyObservers({What should be here ?} , ooChange, Pointer(Edit2.Text));
  80. end;
  81.  
  82. { *** TObserver *** }
  83. constructor TObserver.Create(anActiveobject: TObject);
  84. begin
  85.    inherited Create;
  86.    fActiveObject:= anActiveobject; { assign the working object }
  87. end;
  88.  
  89. destructor TObserver.Destroy;
  90. begin
  91.    fActiveObject:= nil; { release the link }
  92.    inherited Destroy;
  93. end;
  94.  
  95. procedure TObserver.FPOObservedChanged(aSender: TObject;Operation: TFPObservedOperation;Data: Pointer);
  96. begin
  97.   case Operation of
  98.     ooChange: begin
  99.        //Shouldn't be my Sender the Control ? or is it the Observerd Object ?
  100.        //Do i have to assign my Values, which are Sended, all manually to my Data ? (Something like MyDataCLass.Integer1:= TEdit(Sender).Text.ToInteger)
  101.  
  102.        IF aSender is TEdit then begin;
  103.           IF  TEdit(aSender).Equals(Form2.Edit1) then begin
  104.              Form2.Edit3.text:= TEdit(aSender).Text;
  105.           end else if TEdit(aSender).Equals(Form2.Edit2) then begin
  106.              Form2.Edit4.text:= TEdit(aSender).Text;
  107.           end else if TEdit(aSender).Equals(Form2.Edit3) then begin
  108.              Form2.Edit1.text:= TEdit(aSender).Text;
  109.           end else if TEdit(aSender).Equals(Form2.Edit4) then begin
  110.              Form2.Edit2.text:= TEdit(aSender).Text;
  111.           end;
  112.        end;
  113.     end;
  114.     ooFree: begin
  115.        (ASender as IFPObserved).FPODetachObserver(self);
  116.     end;
  117.   end;
  118.  
  119.   {Case aSender.ClassName of
  120.      Form2: begin
  121.         ShowMessage(String(Data));
  122.      end;
  123.   end;}
  124. end;
  125.  
  126. { *** TObserved *** }
  127. constructor TObserved.Create(aSubject: TObject);
  128. begin
  129.   inherited Create;
  130.   fSubject:= aSubject; { just typecast to say, a TEdit, or your own creation :-) }
  131.   fObservers:= nil;
  132.   // ???
  133. end;
  134.  
  135. destructor TObserved.Destroy;
  136. begin
  137.   fSubject:= nil;
  138.   if assigned(fObservers) then begin
  139.      FPONotifyObservers(Self,ooFree,nil);
  140.      FreeAndNil(fObservers);
  141.   end;
  142.   inherited Destroy;
  143. end;
  144.  
  145. procedure TObserved.FPOAttachObserver(aObserver: TObject);
  146. begin
  147.   if not assigned(fObservers) then fObservers:= TFPList.Create; { lazy creation }
  148.   fObservers.Add(pointer(aObserver));
  149. end;
  150.  
  151. procedure TObserved.FPODetachObserver(aObserver: TObject);
  152. begin
  153.   if assigned(fObservers) then begin
  154.     fObservers.Remove(pointer(aObserver));
  155.     if (fObservers.Count = 0) then FreeAndNil(fObservers);
  156.   end;
  157. end;
  158.  
  159. { notify each observer in the list of observers, aSender is the subject, NOT Self }
  160. procedure TObserved.FPONotifyObservers(ASender : TObject; AOperation : TFPObservedOperation; Data : Pointer);
  161. var
  162.    I: integer;
  163.    Obs: TObserver;
  164. begin
  165.    if assigned(fObservers) then begin
  166.       for I:= fObservers.Count-1 downto 0 do begin
  167.          Obs:= TObserver(fObservers[I]);
  168.          Obs.FPOObservedChanged(fSubject,aOperation,Data);
  169.          Obs:= nil;
  170.       end;
  171.    end;
  172. end;
  173.  
  174. end.
  175.  
  176.  

My Goal is to seperate my GUI from my Calculations so i wanted to use the Observer pattern to notify my Controller from GUI and visa versa.
Still stuck at the Basics. Should every of my Controls on GUI be of type Observed ? Is there 1 Observer Enought for each GUI(Form) i have ? Or do i have to implement an Observer for every "Observed" Control ?

Would be great if anyone could watch (i left some Comments) where im stuck, and tell me what im missing.

Thank you in advance.
« Last Edit: November 02, 2021, 05:10:24 pm by Weitentaaal »
Lazarus: 2.0.12 x86_64-win64-win32/win64
Compiler Version: 3.2.2

linnemann

  • New Member
  • *
  • Posts: 34
Re: Observers
« Reply #11 on: November 02, 2021, 08:20:36 pm »
I started where you are a few years ago and instead of doing it all by hand i went the tiOPF route.

It has quite some good basic documentation about how to do this decoupling and helps unstand the basics about observer/visitors/subjects whatever you want to call it.

http://tiopf.sourceforge.net/Doc/QuickStart/mgm/index.html

Especially read these articles.

http://geldenhuys.co.uk/articles/

SymbolicFrank

  • Hero Member
  • *****
  • Posts: 1313
Re: Observers
« Reply #12 on: November 02, 2021, 09:41:25 pm »
I think the main complication is if you want to contact other threads/processes. And if they run the same code on the same platform. Because that's most of the time why you would want this. To give the GUI thread status updates, or update the state and availability of the other threads/services.

On the same machine, IPC (named pipes on *nix, shared memory on Windows) is easiest. In all other cases, sockets make a lot of sense. On windows that's also a lot easier. But there's a hidden catch-22 in that you also want some manager thread/process to get all the updates and be able to send commands. The amount of observers tends to explode, if you're not careful.

 

TinyPortal © 2005-2018