OOP Problem - gute Struktur/Implementierung gesucht

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von Nimral »

Hi,

ich habe eine Applikation, die aus einem Mainform und x Frames (Frame1 of TFrame1, Frame2 of TFrame2 usw.) besteht. Es ist immer nur ein Frame sichtbar (oder auch mal keiner ...). Ausgewählt wird im einfachsten Fall per Menü bzw. Button. Ich wollte das so umsetzen dass das Zeigen eines bestimmten Frames durch Zuweisung des Frames an eine Property im MainForm erfolgt.

Code: Alles auswählen

Type TFormMain = class(TForm)  
        ....
        FActiveFrame: TFrame;
	Property ActiveFrame : TFrame read FActiveFrame write SetActiveFrame.
	....
	end;

procedure TFormMain.FormCreate(Sender: TObject); 

begin
   FActiveFrame := nil;   // brauchts das überhaupt??? Nil = default?
...
   ActiveFrame := Frame1;
end;	
procedure TFormMain.onButton1Click(Sender: TObject);

begin
   ActiveFrame := Frame1;
end;

procedure TFormMain.onButton2Click(Sender: TObject);

begin
   ActiveFrame := Frame2;
end;
	
Soweit mir bekannt und erfolgreich getestet knipst man Frames an und aus indem man deren Parent Eigenschaft auf MainForm bzw self setzt (nil=Frame unsichtbar). Weiters möchte ich, dass die Frames zur Laufzeit erzeugt werden, sofern sie jemals aufgerufen werden. Für irgendwelche Initialisierungen/Cleanups überschreibe ich den Constructor/Destructor des jeweiligen Abkömmlings von TFrame.

Mein Plan war, so vorzugehen:

Code: Alles auswählen

Procedure TFormMain.SetActiveFrame(AFrame:TFrame);

begin
  if FActiveFrame = AFrame then exit;
  if assigned(FActiveFrame) then FActiveFrame.Parent := nil;  // letztes gezeigtes Formular verbergen
  if AFrame  = nil then exit;
  // Frame initialisieren wenn nötig
  if not assigned(AFrame) then
    begin
    if AFrame is TFrame1 then
       FActiveFrame := TFrame1.Create(self)
   else if AFrame is TFrame2 then
       FActiveFrame :=  TFrame2.Create(self);
   ...
   else
       assert(false,'TMainForm.SetActiveFrame: invalid frame type passed');
     end
   else
     FActiveFrame := AFrame;    
   FActiveFrame.Parent := self;
end;
Klappt natürlich nicht. Innerhalb von SetActiveFrame ist AFrame is TFrame1 immer false, weil AFrame zu TFrame "downgraded" wurde (sorry, der OOP Slang Ausdruck dafür fällt mir gerade nicht ein). Mit meinem derzeitigen (Halb)wissen fällt mir nichts besseres ein als zu versuchen, SetActiveFrame zu überladen für jeden Frame-Typen:

Code: Alles auswählen

Procedure TFormMain.SetActiveFrame(AFrame:TFrame1);
Procedure TFormMain.SetActiveFrame(AFrame:TFrame2);
aber Hand aufs Herz, das kanns ja wohl nicht sein. Ich denke, was ich machen wollte ist legitim und OOP konform, es sollte also eine OOP konforme Lösung geben, aber ich komme nicht drauf wie ich sie formulieren muss. Ich müsste "irgendwie" den Frame-Typen an ActiveFrame übergeben ...

Wer hat mir einen Tipp wie das geht? Oder wie ich eine solche Struktur generell designen muss damit ich sie dann so verwenden kann wie ich das oben skizzert habe.

Thnx, Armin.

Benutzeravatar
fliegermichl
Lazarusforum e. V.
Beiträge: 1435
Registriert: Do 9. Jun 2011, 09:42
OS, Lazarus, FPC: Lazarus Fixes FPC Stable
CPU-Target: 32/64Bit
Wohnort: Echzell

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von fliegermichl »

Das kannst durch eine Variable vom Typ TFrameclass lösen.

Code: Alles auswählen

type
 TFrameClass = class of TFrame;
 
procedure SetActiveFrame(Frame : TFrameClass);
begin
 fActiveFrame := Frame.Create(Application);
 fActiveFrame.Parent := self;
end;

// In der Routine, die entscheidet, was erzeugt wird, kannst du dann den speziellen Klassentyp angeben
procedure ErzeugeFrame(Sender : TObject);
begin
 if Sender = Button1 then SetActiveFrame(TFrame1) else
 if Sender = Button2 then SetActiveFrame(TFrame2) else
 ...
end;

wp_xyz
Beiträge: 4885
Registriert: Fr 8. Apr 2011, 09:01

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von wp_xyz »

Zunächst mal: Wenn du einen aktuell nicht mehr verwendeten Frame bestehen lassen willst, dann brauchst du eine Aufbewahrungsmöglichkeit, etwa ein FrameList: TObjectList, oder sowas, denn durch die Zuweisung eines neu erzeugten Frames an FActiveFrame ist der Frame, der bisher ActiveFrame war, nicht mehr erreichbar.

Aber was ist das Problem, einen Frame zu zerstören, wenn er nicht mehr gebraucht wird?

Da es immer nur einen einzigen Frame gibt, würde ich der Prozedur SetActiveFrame nicht eine Frame-Instanz, sondern die gewünschte Frame-Klasse übergeben. Bei deiner Lösung ist ja das Problem dass du den Frame schon erzeugt haben musst, bevor du ihn in SetActiveFrame neu erzeugst (seltsame Konstruktion...).

Also (von der Syntax nicht getestet):

Code: Alles auswählen

type
  TFormFrame = class(TFrame)
  ...
  TFrame1 = class(TFormFrame)
  ...
  TFrame2 = class(TFormFrame);
  // usw. 
  TFormFrameClass = class of TFormFrame;  // Klasse aller Frames, die an SetActiveFrameübergeben werden können
  
procedure TMainForm.SetActiveFrame(AFrameClass: TFormFrameClass);
begin
  if FActiveFrame is AFrameClass then exit;
  FActiveFrame.Free;
  FActiveFrame := AFrameClass.Create(Self);
  FActiveFrame.Parent := self;
  ...
end; 
P.S.
fliegermichl war schneller...

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von Michl »

Alternativ zu o.g. Vorgehen kann man auch Creation on Demand machen. Das hat zwar den Nachteil, daß der Debugger immer in die Funktion springt, jedoch den Vorteil, daß das Frame immer von überall und quasi jederzeit zur Laufzeit verfügbar ist:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowFrame(Frame1);
end;

procedure TForm1.ShowFrame(AFrame: TFrame);
begin
  if (Panel1.ControlCount > 0) then
    if Panel1.Controls[0] = AFrame then
      Exit
    else
      Panel1.Controls[0].Parent := nil;
  AFrame.Align := alClient;
  AFrame.Parent := Panel1;
end; 
...
  TFrame1 = class(TFrame)
  ...
  end;

function Frame1: TFrame1;

implementation

var
  FFrame1: TFrame1 = nil;

function Frame1: TFrame1;
begin
  if not Assigned(FFrame1) then FFrame1 := TFrame1.Create(Application.MainForm);
  Result := FFrame1;
end;
Kurz zusammengehacktes Beispiel anbei.
Dateianhänge
TestFrames.zip
(3.97 KiB) 52-mal heruntergeladen

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von Nimral »

Danke euch wieder mal für eure Tipps, ich probiere es nachher gleich mal aus und sehe, welcher Ansatz mir am besten gefällt, jetzt muss ich eine Weile weg von den Tasten.

Nachtrag: ich habe den Code oben nur ins Forum geschrieben, sorry, und dabei ein Detail nicht erwähnt: per Default macht Lazarus ja für jedes Frame eine eigene Unit. Ich habe in der Unit das Frame deklariert und initialisiert:

Code: Alles auswählen

Unit Frame1Unit;

Type TFrame1=Class(Tframe)
   ...
   end;

var Frame1: Tframe1;

...

initialization
   Frame1 := nil;   // brauchts das überhaupt, oder ist NIL default? 
die von WP vermisste Aufbewahrungsmöglichkeit wäre also gegeben.

In ShowFrame würden die Variablen nach der Create Zeile zugewiesen. Das ändert aber nichts an dem eigentlichen Problem, dass der is Test fehlschlägt, das würde mich also nicht retten. Tut mir leid, wenn ich dachte, ich könnte mir das Erstellen eines Demo-Projekts sparen. AWL: es braucht immer ein Demo-Projekt, und eigentlich wärs nicht mal aufwändiger als der Versuch, den Rumpf-Code in Worten zu beschreiben. Ich machs künftig besser, versprochen.

HG, und bis in etwa 2 Stunden, Armin.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6208
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von af0815 »

Die deklaration und das Init mit nil kannst du dir sparen.

Ein Frame ist ganz einfach ein Objekt wie jedes andere, du musst dir halt das 'Object=Frame' merken. Lebenszeit ist genauso wie bei allen anderen und wenn du dir das Objekt nicht merkst, dann gibt es ein Speicherleck, wie bei allen anderen Objekten.

Grundlegend 2 Ansätze, wie schon gesagt:
*) Frame erzeugen, benutzen und wieder freigeben
*) In einer Liste die Frames speichern und erst später freigeben, lassen sich wie alle Objekte speichern.

Der einzige Unterschied ist, das man dem Frame sagen muss, wer der Parent ist damit es sich darstellen kann. Ich erzeuge die Frames meist beim Start, alle auf einmal (ist bei 20-30 Frames kein Problem). Der Code sieht in etwa so aus:

Code: Alles auswählen

  if not assigned(FrameLogin) then begin
    try
      // Frame Login
      FrameLogin:= TFrameLogin.Create(TS_Login);
      FrameLogin.Parent:= TS_Login;
      FrameLogin.Align:= alClient;
      FrameLogin.XMLPropStorage:= XMLPropStorage1;
      FrameLogin.DefaultUser:= MyLastUser;
    except
      //
    end;
  end;
Was ich noch mir gemacht habe. Eine BasisFrame Klasse, wo Standardsachen, die ich sowieso bei fast jeden Frame brauche - wie den XMLPropStorage und aktuellen Benutzer - gleich mit den Routinen hinterlegt sind. Damit brauche ich an einige Dinge nicht mehr denken, wenn ich ein Frame verwende.

Viele Wege führen nach Rom ... (aber noch mehr in die Irre) :mrgreen:
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von Nimral »

Ha, zwischen Tür und Angel ...

ich wollte das in diesem Fall so machen, wegen ein paar unagenehmen "Kundenwünschen". In dem Fall mach das sogar Sinn, weil als Backend nur eine Dateistruktur zur Verfügung steht und keine Datenbankmaschine. Kundenwunsch ist Kundenwunsch, und er hat durchaus auch seine Gründe warum er es so haben will. Die eigentlichen Daten stecken in mehreren 10.000 kleinen Dateien.

Einige der Frames stellen ellenlange Listen als Tabellen dar. Immerhin kann man den Listeninhalt aus den Dateinamen erzeugen. Außerdem will der Kunde eine Filtermöglichkeit, bei der "on thy fly" (also ohne Nachladen der Daten vom Backend) die Anzeige beim Ausfüllen eines TEdit gefiltert wird --> Lazy Load ist nicht, ich muss wohl oder übel alle Datensätze laden, spätestens wenn der Filter ausgefüllt wird, und der wird eigentlich immer ausgefüllt, niemand scrollt sich durch die ellenlange Liste (ca. 5000 Einträge) um einen Datensatz zu finden. Das Laden vom FIlesystem dauert natürlich seine Zeit, ist aber, wenn man nicht gleich alle Listen lädt, im Rahmen einiger Sekunden (getestet mit 32.000 Datensätzen) und damit sehr erträglich. Das Handling des Ganzen ist eigentlich sehr angenehm, immerhin sieht man nach jedem Zeichen, das man in die Filterzeile eingibt sofort, wie viele mögliche Suchtreffer man hat. Um ein wenig effizient zu bleiben beginnt der FIlter erst zu wirken, wenn mindestens zwei oder drei Zeichen eingegeben wurden.

Daher erzeuge ich die Frames erst, wenn der entsprechende Button betätigt wurde, lade im Constructor die Daten, und lasse sie danach zur weiteren Verwendung im Speicher bis das Programm beendet wurde. Speicherleck sollte ich keins bekommen, da beim Create ja der Owner gesetzt wird --> das Frame wird bei Ende des Hauptformulars automatisch freigegeben, und ich räume Daten die ich selber geladen habe im Destructor ggf. wieder auf.

Natürlich könnte ich die Frames auch leer erzeugen und dann später vor dem Show mit Daten füllen, was immerhin den Vorteil hätte dass ich dann bei jedem Formularwechsel auch nachsehen könnte, ob sich die Dateistruktur unvorhergesehener Maßen verändert hat, aber das sind alles Varianten die mit dem eigentlichen Problem nichts zu tun haben. Mir scheint im Moment dieses "Class of Object" konstrukt vielversprechend, es mag mir eventuell nicht immer und überall die beste Lösung bieten, aber es scheint so dass ich damit das was ich haben wollte erreiche. Mal sehen, wie ich damit zurecht komme.

Armin.

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von Nimral »

Also, ich wär zufrieden.

Hier der Code, mit Bitte um Kommentar, ob jemand darin etwas böses oder verwerfliches sieht:

Code: Alles auswählen

unit MainUnit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, ComCtrls,
  Buttons;

type
  TFrameClass = Class of TFrame;

type

  { TMainForm }

  TMainForm = class(TForm)
    Panel1: TPanel;
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    SpeedButton3: TSpeedButton;
    SpeedButton4: TSpeedButton;
    SpeedButton5: TSpeedButton;
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure SpeedButton2Click(Sender: TObject);
    procedure SpeedButton3Click(Sender: TObject);
    procedure SpeedButton4Click(Sender: TObject);
    procedure SpeedButton5Click(Sender: TObject);
  private
    FActiveFrame: TFrame;
  public
    Procedure SwitchToFrame(AFrameClass:TFrameClass);
  end;

var
  MainForm: TMainForm;

implementation

{$R *.lfm}

uses Unit1,Unit2,Unit3,Unit4;

{ TMainForm }

procedure TMainForm.FormCreate(Sender: TObject);

begin
  SwitchToFrame(TFrame1);
end;

procedure TMainForm.SpeedButton1Click(Sender: TObject);
begin
  SwitchToFrame(TFrame1);
end;

procedure TMainForm.SpeedButton2Click(Sender: TObject);
begin
  SwitchToFrame(TFrame2);
end;

procedure TMainForm.SpeedButton3Click(Sender: TObject);
begin
  SwitchToFrame(TFrame3);
end;

procedure TMainForm.SpeedButton4Click(Sender: TObject);
begin
  SwitchToFrame(nil);
end;

procedure TMainForm.SpeedButton5Click(Sender: TObject);
begin
  SwitchToFrame(TFrame4);
end;

procedure TMainForm.SwitchToFrame(AFrameClass: TFrameClass);

begin
  if FActiveFrame is AFrameClass then exit; // no change
  if assigned(FActiveFrame) then FActiveFrame.parent := nil;  // hide previous frame
  if not assigned(AFrameClass) then exit;  // no new frame to display
  // Create frame if required and display it
  if AFrameClass = TFrame1 then
     begin
     If not assigned(Frame1) then
        Frame1 := TFrame1.Create(self);
     FActiveFrame := Frame1;
     end
  else if AFrameClass = TFrame2 then
     begin
     If not assigned(Frame2) then
        Frame2 := TFrame2.Create(self);
     FActiveFrame := Frame2;
     end
  else if AFrameClass = TFrame3 then
     begin
     If not assigned(Frame3) then
        Frame3 := TFrame3.Create(self);
     FActiveFrame := Frame3;
     end
  else
     assert(false,'TMainForm.SwitchToFrame: invalid frame class ' + AFrameClass.ClassName);
  FActiveFrame.Parent := self;
end;

end.
Mit gefällt die Lösung insofern, dass der ganze Frame-abhängige Klatsch in einer einzigen Routine SwitchToFrame in einer leicht zu pflegenden if ... else if Leiter abgefackelt wird. Case .. of wäre schöner gewesen, klappt aber leider nicht mit Objekten. Der Rest steckt in einem onClick pro Button, und ansonsten in der zugehörigen Unit. Die Fingerübung mit dem Überschreiben des Frame-Constructors und Destructors für init und destroy von frame-spzifischen Datenstrukturen habe ich mir gespart, ich erwarte von da keine Probleme.

Speicherlecks bekomme ich keine gemeldet.

Ich finde den Lösungsweg gut, bis auf das "Class of Object" Konstrukt (Wer es so wie ich erst suchen muss, siehe: "Class Reference Types" in der Object Pascal Doku) habe ich keine Kopfstände gebraucht und keine exotischen Konstruktionen. Noch schöner wäre nur gewesen, wenn ich die Frames, obwohl sie noch gar nicht created waren, so wie ich es ursprünglich vor hatte, direkt an SwitchToFrame hätte übergeben können, einen Typen zu übergeben ist doch eher unlogisch, fast schon ein Workaround, und klappt natürlich nur weil man sich hier sicher sein kann, dass von jedem Frame-Typen nur eine einzige Instanz erzeugt wird. Mal sehen, ob die anderen Lösungsvorschläge da noch was bringen, was mir nach meinem(!) Gefühl noch eine angenehmere Lösung bietet.

HG, Armin.
Dateianhänge
FrameTest.zip
(130.25 KiB) 54-mal heruntergeladen

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von Nimral »

Jetzt wirds nochmal etwas spannender.

Nur so aus Interesse und Neugier habe ich mich daran erinnert, wie ich das früher angegangen wäre: wenn der Typ eines Übergabeparameters variieren soll, nimm einen Pointer!

Code: Alles auswählen

unit MainUnit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, ComCtrls,
  Buttons;

type
  TFrameClass = Class of TFrame;
  PFrame = ^TFrame;

type

  { TMainForm }

  TMainForm = class(TForm)
    Panel1: TPanel;
    SpeedButton1: TSpeedButton;
    SpeedButton2: TSpeedButton;
    SpeedButton3: TSpeedButton;
    SpeedButton4: TSpeedButton;
    SpeedButton5: TSpeedButton;
    StatusBar1: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure SpeedButton1Click(Sender: TObject);
    procedure SpeedButton2Click(Sender: TObject);
    procedure SpeedButton3Click(Sender: TObject);
    procedure SpeedButton4Click(Sender: TObject);
    procedure SpeedButton5Click(Sender: TObject);
  private
    FActiveFrame: PFrame;
  public
    Procedure SwitchToFrame(PAFrame:PFrame;AFrameClass: TFrameClass = nil);
  end;

var
  MainForm: TMainForm;

implementation

{$R *.lfm}

uses Unit1,Unit2,Unit3,Unit4;

{ TMainForm }

procedure TMainForm.FormCreate(Sender: TObject);

begin
  FActiveFrame := nil;
  SwitchToFrame(@Frame1,TFrame1);
end;

procedure TMainForm.SpeedButton1Click(Sender: TObject);
begin
  SwitchToFrame(@Frame1,TFrame1);
end;

procedure TMainForm.SpeedButton2Click(Sender: TObject);
begin
  SwitchToFrame(@Frame2,TFrame2);
end;

procedure TMainForm.SpeedButton3Click(Sender: TObject);
begin
  SwitchToFrame(@Frame3,TFrame3);
end;

procedure TMainForm.SpeedButton4Click(Sender: TObject);
begin
  SwitchToFrame(nil);
end;

procedure TMainForm.SpeedButton5Click(Sender: TObject);
begin
  SwitchToFrame(@Frame4,TFrame4);
end;

procedure TMainForm.SwitchToFrame(PAFrame:PFrame;AFrameClass: TFrameClass = nil);

begin
  If FActiveFrame = PAFrame then exit;
  if assigned(FActiveFrame) then FActiveFrame^.parent := nil;  // hide previous frame
  if not assigned(AFrameClass) then exit;  // no new frame to display
  // Initialize frame if required and display it
  if not assigned(PAframe^) then
     PAFrame^ := AFrameClass.Create(self);
  FActiveFrame := PAFrame;
  FActiveFrame^.Align := alCLient;
  FActiveFrame^.Parent := self;
end;

end. 
Das funktioniert, ist schön kurz, und behandelt übrigens den Negativ-Testframe Frame4 aus dem letzten Beispiel auch gleich richtig, man braucht also exakt null Code zu ändern wenn ein Frame dazu kommt. Aber ich musste eine Kröte schlucken, nämlich die Frameklasse als 2. Parameter übergeben, damit ich sie beim Create zur Verfügung habe. Aber eigentlich ist doch alles längst bekannt: das per PAFrame übergebene Objekt, z.B. Frame1, ist per Definition in Unit1 ja bereits auf TFrame1 festgelegt:

Code: Alles auswählen

var
  Frame1:TFrame1; 
... und es ist ein Objekt, also schleppt es doch diese ganzen Metadaten von Objekten mit sich herum, und da gab es doch diesen schlauen Mechanismus namens "Reflection" (oder war das c#) mit dem man solche grenzgängigen Konstrukte relativ übersichtlich umsetzen kann?

Ich sehe keinen Grund, warum so ein Code nicht schreibbar sein sollte:

Code: Alles auswählen

procedure TMainForm.SwitchToFrame(PAFrame:PFrame);


var
  AFrameClass: TClass;

begin
  If FActiveFrame = PAFrame then exit;
  if assigned(FActiveFrame) then FActiveFrame^.parent := nil;  // hide previous frame
  if not assigned(PAFrame) then exit;
  // Initialize frame if required and display it
  if not assigned(PAframe^) then
     begin
     AFrameClass := PAFrame^.ClassType;         // Das geht leider so nicht, aber vielleicht geht es anders?
     PAFrame^ := AFrameClass.Create(self);
     end;
  FActiveFrame := PAFrame;
  FActiveFrame^.Align := alCLient;
  FActiveFrame^.Parent := self;
end;
Hat jemand eine Idee ob Object Pascal so eine Konstruktion irgendwie hergibt?

Thnx, Armin.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6208
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von af0815 »

Du bist meiner Meinung nach das Pattern factories zu erfinden :D

Ist nicht böse gemeint, sondern deiner Erkenntnis geschuldet. Ja das gibtes auch in Pascal und wird auch häufig verwendet. Besonders bei Reportengines wie fpreport bin ich laufen darüber gestolpert.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von Nimral »

Ich bin ganz und gar nicht böse!

Über das Factory Pattern habe ich auch - reflexhaft - als erstes nachgedacht, schließlich hat es auf den ersten Blick alles was die gesuchte Lösung haben müsste, incl. einer guten Presse und das Backing der GOF.

Auch nicht böse gemeint, und ich würde Dir die Füße küssen (verbal) für ein Codeschnippsel das genau mein Problem mit einem Factory Pattern löst, und das besser macht wie mein Ansatz mit dem Pointer. Es löst, genau betrachtet, ein verwandtes, aber dennoch anderes Problem.

Drei Singletons (bzw. so viele SIngletons wie ich Frames habe) wären ein Ansatz, der vermutlich im Rahmen der Patterns am Ehesten geeignet wäre - allerdings mit deutlichem Mehraufwand und auf Kosten der Lesbarkeit des Codes.

Der hat - eben bis auf die Unschönheit dass ich zwei Parameter brauche und nicht gesichert ist dass nicht jemand (mein ich in 5 Jahren) so blöd ist, innerhalb der selben Zeile sowas wie "SwitchToPanel(@Frame1,TFrame2)" oder "SwitchToPanel(Frame1,TFrame1)" zu schreiben. Er ist, so nennt man das wohl, nicht type-safe. Ein einziger Parameter für alles wäre mir am Liebsten gewesen, aber da sehe ich im Moment kein Licht.

Oder ich machs eben, wie schon mal geschrieben, ganz konventionell, alle Frames im onCreate des MainForms billig createn, und das Laden der Daten und das Anzeigen des Frames einer eigenen Routine überlassen die erst aufgerufen wird wenn das Frame angezeigt werden soll.

Armin.

Thandor
Beiträge: 153
Registriert: Sa 30. Jan 2010, 18:17
OS, Lazarus, FPC: Windows 10 64Bit/ lazarus 3.0 mit FPC 3.2.2 (32Bit + 64bit)
CPU-Target: 64Bit
Wohnort: Berlin

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von Thandor »

Hallo,

werden Instanzvariablen von Klassen (also Objekte) nicht grundsätzlich wie Pointer behandelt? Bzw. Sind Pointer auf die Objektstruktur?

Code: Alles auswählen

program Objekte;


type tcMyClass = class
  MyAttribute : String;
  constructor Create(aText : String);
end;

constructor tcMyClass.Create(aText : String);
begin
  MyAttribute := aText;
end;


procedure DoSomethingWith(Instance : tcMyClass);
begin
  Instance.Free();
  Instance := tcMyClass.Create('Hello You!');
end;



var MyInstance : tcMyClass;

begin
  MyInstance := tcMyClass.Create('Hello World!');
  WriteLn(MyInstance.MyAttribute);  // Ausgabe: Hello World!

  DoSomethingWith(MyInstance);
  WriteLn(MyInstance.MyAttribute);  // Ausgabe: Hello You!

  ReadLn();
end.      
Oder gilt das als schlechte Stiel? Ist das so ein nogo?
So wie ich das sehe kannn mann sich den extra Pointer doch sparen? Das Problem mit der Class of tFrame-Variable löst das natürlich nicht.

Grüße
Thandor

Edit: Ok, wenn ich mein Beispiel noch mal genauer betrachte müsste es Zufallsein, dass es funktioniert. Da in dem kleinen Beispiel das neue Objekt, erstellt in "DoSomethingWith" zufällig an der selben Speicheradresse erstellt werden kann. Das muss nicht immer so sein. Wenn mann den Parameter von "DoSomethinWith" als Var-Parameter deklariert sollte es sicherer sein. procedure DoSomethingWith(var Instance : tcMyClass);

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: OOP Problem - gute Struktur/Implementierung gesucht

Beitrag von Socke »

Thandor hat geschrieben:
Mi 9. Jun 2021, 08:53
Edit: Ok, wenn ich mein Beispiel noch mal genauer betrachte müsste es Zufallsein, dass es funktioniert. Da in dem kleinen Beispiel das neue Objekt, erstellt in "DoSomethingWith" zufällig an der selben Speicheradresse erstellt werden kann. Das muss nicht immer so sein. Wenn mann den Parameter von "DoSomethinWith" als Var-Parameter deklariert sollte es sicherer sein. procedure DoSomethingWith(var Instance : tcMyClass);
Nur mit einem var-Parameter erhälst du vorhersagbares Verhalten.
Andernfalls hast du eine klassiche Use-after-Free Situation geschaffen.
Die Variable MyInstance zeigt nach dem Aufruf von DoSomethingWith() höchstwahrscheinlich nicht auf das neu erzeugte Objekt.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Antworten