AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Trennung von GUI und Logik

Ein Thema von Maekkelrajter · begonnen am 28. Apr 2019 · letzter Beitrag vom 2. Mai 2019
Antwort Antwort
Seite 1 von 3  1 23      
Maekkelrajter

Registriert seit: 8. Mär 2017
Ort: Köln
157 Beiträge
 
Delphi 12 Athens
 
#1

Trennung von GUI und Logik

  Alt 28. Apr 2019, 16:17
Seit vielen Jahren, um nicht zu sagen Jahrzehnten programmiere ich hobbymäßig und vorwiegend für den Eigenbedarf. Das begann 1987 mit dem 64er (Basic, Assembler). Es folgte der Amiga (Basic, GFA-Basic, Assembler) und schließlich diverse PCs ( Assembler,TP 6 , BP 7, Delphi 1, 2, 4, 10). Meist waren das kleinere Anwendungen, für die natürlich vorher kein Konzept erstellt wurde, sondern nach Erstellung der Infrastruktur (GUI, IO-Routinen) mit möglichst wenig Zeitaufwand die gewünschte Funktionalität implementiert wurde. Immer wieder kam es allerdings vor, dass gerade solche q&d hingerotzte Sachen dann im Laufe der Zeit immer weiter wuchsen, um nicht zu sagen wucherten. Dabei versuchte ich natürlich immer, gewisse Mindeststandards einzuhalten, was Strukturierung und Modularisierung betraf, sodass ich auch nach Jahren weitgehend die Übersicht behielt. Da ich das Ganze, wie gesagt, hobbymäßig betreibe, konnte ich auch viel Zeit mit 'Refactoring' verbringen, wobei sowohl GUI als auch der Programmcode immer wieder optimiert und 'verschönert' , soll heißen 'profimäßiger' ( oder was ich dafür hielt) gestaltet wurde. Dennoch fürchte ich, ein professioneller Entwickler würde beim Anblick mancher meiner Sources einen Schreikrampf erleiden, aber damit kann ich leben. Denn für mich ist der Hauptzweck erfüllt: Die Funktionalität ist wunschgemäß, die Optik ist ansprechend und das GUI funktional und gut bedienbar. Ich kann das Programm nutzbringend für mich einsetzen und nicht nur IT-Laien damit schwer beeindrucken
Wie sicher mancher schon ahnt, bin ich auch nicht der Großmeister der OOP.
Daher rührt auch mein aktuelles Problem. In meinem aktuellen Projekt möchte ich eine weitgehende Trennung von GUI und Logik durchführen. Mich stört vor allem die grotesk aufgeblähte Deklaration des Hauptformulars, das einen großen Teil der Logik in den OnClick - Handlern bzw. deren Implementation enthält. Bei eienem großen Teil von ihnen ließ sich der Code leicht in separate Units bzw. Klassen auslagern, was das Ganze schon erheblich übersichtlicher macht. Aber jetzt sollen doch Nägel mit Köpfen gemacht und sämtliche Logik aus der Mainform - Deklaration und -Implementierung entfernt werden.
Ein Problem sind für mich die Routinen, die während der oft sekunden- oder gar minutenlangen Dauer ihrer Ausführung auf Komponenten des Hauptformulars zugreifen und etwa Zählerstände in TLabels oder Textausgaben in TMemos aktualisieren, was als Lebenszeichen der Anwendung, quasi als Fortschritts-Anzeige, und auch zur Information des Anwenders unerläßlich ist.

Beispiel:
Delphi-Quellcode:
unit DemoForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    Memo1: TMemo;
    // 128 Deklarationen
    procedure Button1Click(Sender: TObject);
    // 100 Event- Handler
  private
    { Private-Deklarationen }
    // 116 Deklarationen (schon teilweise reduziert)
  public
    { Public-Deklarationen }
    // 2 Deklarationen
  end;

var
  Form1: TForm1;

implementation


//Beispiel: Durchsuche eine iTunes Mediathek mit 10000 Tracks nach Tracks ohne Albumcover (Pseudocode)

procedure TForm1.Button1Click(Sender: TObject);
  var i: Integer;
begin
  {
  for i := 1 to 10000 do
    begin                                                                                                                               
      label1.caption:= inttostr(i);                                              // erhöhe den Zählerstand im Label                                     
      If Track(i)  ohne Albumcover Then Memo1.lines.add(Titel +  Artist + Album) // Wenn einer gefunden, Track (Titel, Artist, Album) im Memo ausgeben
    end;
  }

end;

end.
Wie schaffe ich es nun, den Code aus dem OnClick - Handler auszulagern und dennoch Zugriff auf Form1 zu ermöglichen?

Auf globale Instanz - Variable Form1 zugreifen?
Jeder ausgelagerten Prozedur/ Methode Form1 als Parameter übergeben?
Ausgelagerte Methoden in Klasse zusammenfassen und in deren Constructor Form1 einer Feldvariablen (z.B. 'FMainform') zuweisen?
Windows-Messages?
Oder was ganz anderes?

Was käme da als halbwegs saubere Lösung in Frage? (außer alles in die Tonne kloppen und von vorne anfangen)

Gruß LP
  Mit Zitat antworten Zitat
TurboMagic

Registriert seit: 28. Feb 2016
Ort: Nordost Baden-Württemberg
2.960 Beiträge
 
Delphi 12 Athens
 
#2

AW: Trennung von GUI und Logik

  Alt 28. Apr 2019, 16:39
Hallo,

spendiere doch deiner Main Form für alle diese Fortschrittsanzeigen und Ausgabebedarfe entsprechende Methoden,
welche die GUI controls aktualisieren. Diese Methoden bekommen die auszugebenden Daten als Parameter übergeben.

Dann erstelle eine neue Unit und erstelle ein Interface, welches genau diese Methodendeklarationen enthält.
Das Hauptformular nutzt diese Unit im interface uses block und in class(TForm) wird das Interface ergänzt:
class(TForm, IMainFormInterface).

Die Geschäftslogikklasse bekommt mittels Constructor dieses Interface übergeben und speichert sich das lokal ab:

constructor TBusinessLogic.Create(MainFormInterface: IMainFormInterface);
begin
inherited Create;

FMainFormInterface := MainFormInterface;
end;

Überall wo die geschäftslogik etwas auf dem GUI ausgeben muss, ruft sie die entsprechende Methode über das Interface auf:

FMainFormInterface .DisplayProgress(50); // 50% Fortschritt...

Dadurch hängt die Geschäftslogik nur noch von dem Interface ab und nicht mehr vom GUI und damit nicht mehr von internen
Änderungen des GUI.
  Mit Zitat antworten Zitat
Neumann

Registriert seit: 6. Feb 2006
Ort: Moers
536 Beiträge
 
Delphi 12 Athens
 
#3

AW: Trennung von GUI und Logik

  Alt 28. Apr 2019, 16:49
Der 'Button1click' macht nur etwas mit der GUI. Den würde ich nicht woanders hin auslagern. Wenn man die Liste noch sonst noch braucht, könnte man diese als Stringlist z.B. in einem Datenmodul definieren.

So wie der Code aussieht, würde wohl nur das Endergebnis für den User sichtbar werden, die Zwischenergebnisse sieht man so wohl nicht.
Ralf
Gruß vom Niederrhein
  Mit Zitat antworten Zitat
Maekkelrajter

Registriert seit: 8. Mär 2017
Ort: Köln
157 Beiträge
 
Delphi 12 Athens
 
#4

AW: Trennung von GUI und Logik

  Alt 28. Apr 2019, 17:33
Der 'Button1click' macht nur etwas mit der GUI.
Das Beispiel soll nur das Problem verdeutlichen. In der tatsächlichen Implementierung umfasst der Code des Handlers 82 Zeilen, verwendet das iTunes COM-Interface und enthält 13 Zugriffe auf das GUI. Das macht vielleicht auch deutlich, warum das unbedingt geändert werden soll.

Der von Turbomagic skizzierte Weg scheint mir sehr vielversprechend. So etwas schwebte mir auch vor, nur hatte ich keinen Schimmer, wie das zu realisieren wäre. Interfaces kenne ich nur dem Namen nach, sie werden ja auch vom iTunes COM-Interface verwendet, aber da ist ja schon alles fertig in der iTunes Typelibrary vorhanden.

Gruß LP

Geändert von Maekkelrajter (28. Apr 2019 um 17:58 Uhr)
  Mit Zitat antworten Zitat
hum4n0id3

Registriert seit: 28. Sep 2018
5 Beiträge
 
#5

AW: Trennung von GUI und Logik

  Alt 29. Apr 2019, 07:24
Ich bin in Delphi/Lazarus selbst ein Anfänger, weil ich nur hin und wieder etwas damit mache. Hauptsächlich arbeite ich mit PHP und mache dort OOP. Deshalb vielleicht den "Ratschlag" nicht so ganz ernst nehmen. Aber ich mache es so.

1. Ich erstelle eine Unit mit Klasse-Name-XXX. Das enthält meist die Logik für meine Software. ZB.:

Delphi-Quellcode:
unit UDesktopFile;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, FileUtil;

type
  TDesktopFile = class
    private

    public
      function GetAllFiles() : TStringList;

  end;

implementation

function TDesktopFile.GetAllFiles(): TStringList;
var
  StrList: TStringList;
  pathToFiles: String;
begin
  pathToFiles := GetUserDir + '/.local/share/applications/';
  StrList := TStringList.Create;

  FindAllFiles(StrList, pathToFiles, '*.desktop');
  GetAllFiles := StrList;
end;

end.
2. Ich verwende die Unit in meinem Hauptformular, oder einem anderem Formular, mit den Componenten im Formular.

Delphi-Quellcode:
procedure TFrmMainWindow.FormCreate(Sender: TObject);
var
  DesktopFile: TDesktopFile;
begin
  DesktopFile := TDesktopFile.Create;

  try
    LbDesktopFiles.Items.Assign(DesktopFile.GetAllFiles);
  finally
    DesktopFile.Free;
  end;

end;
Das ist jetzt kein Delphi, sondern Lazarus aber funktioniert für mich gut. Aber ob es so richtig ist und so gemacht wird, weiß ich nicht
Jedenfalls sind meine Formulare dadurch sauberer, bzw. übersichtlicher und für Änderungen muss ich die entsprechenden Units besuchen.

MfG
  Mit Zitat antworten Zitat
Benutzerbild von haentschman
haentschman

Registriert seit: 24. Okt 2006
Ort: Seifhennersdorf / Sachsen
5.388 Beiträge
 
Delphi 12 Athens
 
#6

AW: Trennung von GUI und Logik

  Alt 29. Apr 2019, 08:48
Moin...
Ich bin für folgendes:

1. Trennung in die Form und die Logic (seperate Units)
2. Die Eventhandlandler gehören in die Form.
3. Die GUI Sachen gehören in die Form.
4. Die Logic kennt die Form nicht! ...auch kein Interface.
5. Die Form gibt im Eventhandler den "Befehl" an die Logic. "
6. Die Logic holt die Daten und übergibt sie an ein selbst erstelltes Event oder gibt die Daten an das Funktionsresult zurück.
7. Mit Event: Die Form hat den Eventhandler der Logic implementiert und übergibt die Daten an die GUI.
...fertsch.
PS: Der Kreativität sind keine Grenzen gesetzt.



constructor TBusinessLogic.Create(MainFormInterface: IMainFormInterface); Das mit dem Interface halte ich für gewagt. Wenn sich an dem GUI Interface was ändert, mußt du auch immer an die Logik dran...Das zu verhindern ist ja der Sinn der Trennung von GUI Und Logik.

PS: Ausnahmen bestätigen die Regel...

Geändert von haentschman (29. Apr 2019 um 08:50 Uhr)
  Mit Zitat antworten Zitat
hoika

Registriert seit: 5. Jul 2006
Ort: Magdeburg
8.276 Beiträge
 
Delphi 10.4 Sydney
 
#7

AW: Trennung von GUI und Logik

  Alt 29. Apr 2019, 09:55
Hallo,

[B]class [/B]function GetAllFiles() : TStringList; Dann musst Du die Klasse im Formular nicht mal erstellen,
sondern per
LbDesktopFiles.Items.Assign([B]T[/B]DesktopFile.GetAllFiles); aufrufen.

Wobei ich mir nicht sicher bin,
ob die hier einen memory leak erzeugst.
Das Assign kopiert den Inhalt der TStringList.

Ich bin immer dafür, dass der der ein Objekt erzeugt,
auch für dessen Freigabe verantwortlich ist.
Das Formular würde also die StringList erzeugen, an die GetAllFiles als Parameter übergeben,
und wenn es die StringList nicht mehr braucht, wieder freigeben.
Heiko
  Mit Zitat antworten Zitat
Jumpy

Registriert seit: 9. Dez 2010
Ort: Mönchengladbach
1.737 Beiträge
 
Delphi 6 Enterprise
 
#8

AW: Trennung von GUI und Logik

  Alt 29. Apr 2019, 10:19
Das ganze Thema schreit für mich aber auch nach 'Threads'. Dementsprechend sollte vielleicht solche Dinge in eigene Threads ausgelagert werden, die dann auch nur periodisch die GUI aktualiesieren und nicht ständig.
Ralph
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli
Online

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.343 Beiträge
 
Delphi 11 Alexandria
 
#9

AW: Trennung von GUI und Logik

  Alt 29. Apr 2019, 12:17
Man muss hier m.E. drei Dinge separat betrachten...


1) Trennung GUI/BL:

Dazu reicht es aus, sämtliche Logik in eine Klasse TMyBL zu stecken und dort so viele Eigenschaften und Methoden zu veröffentlichen, dass die GUI sich dort umfassend bedienen kann.
Die GUI darf also nie z.B. einen Kontostand erhöhen, indem sie den aktuellen Wert abruft, einen Betrag dazu rechnet und das Ergebnis wieder speichert, sondern sie muss TMyBL.AddMoney(x) aufrufen.
ALLES was an Daten zu ändern oder abzufragen ist, muss über Methoden und Eigenschaften möglich sein.
TMyBL muss also in sich das vollständige Projekt abbilden und komplett funktionsfähig sein.
Natürlich ist TMyBL hilflos ohne Ansteuerung von außen, aber alle Erfordernisse müssen dort vorhanden und nutzbar sein.
Die GUI (Egal ob VCL, FMX oder anderes) stößt dann lediglich Aktionen an und ruft Daten ab für die eigene Darstellung.

Wenn man mit Interfaces umgehen kann ist das sicherlich hilfreich, aber ein klassisches TMyBL-Objekt würde auch reichen.

Eine Trennung von GUI und BL wäre damit schon erreicht.


2) Threads:

Egal, ob eine längere Berechnung im Eventhandler des Formulars erfolgt oder in der oben beschriebenen TMyBL-Klasse, das Formular wird während dieser Zeit hängen bleiben.
Um das zu vermeiden, muss man mit Threads arbeiten oder notfalls mit Timern oder gar Application.ProcessMessages.
Hier ist zu beachten, dass die TMyBL davon nicht abhängig sein darf. Am besten sollte sie davon gar nichts mitbekommen.


3) Kommunikation

Vor allem, wenn GUI und BL in getrennten Threads oder gar Prozessen laufen, muss man sich über die Kommunikation Gedanken machen.
Die GUI darf die BL kennen und dort Änderungen anschieben aber sie sollte gar nicht wissen, was sie da eigentlich tut und warum.
Die BL sollte aber gar nichts von der GUI wissen.
Es muss aber einen abstrakten Informationsaustausch geben, also am besten über einen Framework, das zwischen beiden Seiten (möglichst automatisiert) vermittelt.
Die Frage ist, wie man das am besten organisiert (je nach Anspruch und Möglichkeiten).
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
hum4n0id3

Registriert seit: 28. Sep 2018
5 Beiträge
 
#10

AW: Trennung von GUI und Logik

  Alt 29. Apr 2019, 13:19
Hallo,

[B]class [/B]function GetAllFiles() : TStringList; Dann musst Du die Klasse im Formular nicht mal erstellen,
sondern per
LbDesktopFiles.Items.Assign([B]T[/B]DesktopFile.GetAllFiles); aufrufen.

Wobei ich mir nicht sicher bin,
ob die hier einen memory leak erzeugst.
Das Assign kopiert den Inhalt der TStringList.

Ich bin immer dafür, dass der der ein Objekt erzeugt,
auch für dessen Freigabe verantwortlich ist.
Das Formular würde also die StringList erzeugen, an die GetAllFiles als Parameter übergeben,
und wenn es die StringList nicht mehr braucht, wieder freigeben.
Na wunderbar! Danke für das Kommentar
Und ich denke auch das ich ein Memory Leak erzeuge, jedenfalls nach meinem bisherigem Verständnis. Das TStringList, nach meinem Verständnis, wird nicht (sauber) geleert. Vermutlich nur das DesktopFile. Meine bisherigen Bemühungen schlugen aber bisher Fehl. Aber dafür mache ich mal bei Gelegenheit einen eigenen Thread auf.

Danke! Wieder was gelernt! Tolles Forum.

MfG
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 3  1 23      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:55 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz