Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi OOP wirklich nicht möglich? (https://www.delphipraxis.net/194060-oop-wirklich-nicht-moeglich.html)

Delbor 12. Okt 2017 14:56

Delphi-Version: XE8

OOP wirklich nicht möglich?
 
Hi zusammen

Mit folgender Klasse speichere ich die von einem SQLStatement von MySQL zurückgegeben Daten zwischen:
Delphi-Quellcode:
type
  TQueryResultClass = Class(TPersistent)
  private
    FidBild: Integer;
    FThumbnail : TMemorystream;
    FBitmap: TMemorystream;
    FBildDescribeTabelle : TBildDescribeTabelle;
    FKategoryTabelle : TKategoryTabelle;
    FPass: String;
    FUser: String;
    procedure SetBitmap(Value: TMemoryStream);
    procedure SetThumbnail(Value: TMemoryStream);

    function GetBitmap: TMemoryStream;
    function GetThumbnail: TMemoryStream;
    function FillThumbnail(var Thumbnail: TMemoryStream): TMemoryStream;

  public
    constructor Create(AOwner: TComponent); //
    destructor Destroy; override;
    procedure Assign(Source:TPersistent); override;
    property IdBild :integer read FidBild write FidBild;
    property Thumbnail: TMemoryStream read GetThumbnail write SetThumbnail;
    property Bitmap: TMemoryStream read GetBitmap write SetBitmap;
    property BildDescribeTabelle : TBildDescribeTabelle read FBildDescribeTabelle write FBildDescribeTabelle;
    property KategoryTabelle : TKategoryTabelle read FKategoryTabelle write FKategoryTabelle;
  end;
Nun arbeite ich daran, die AnwendungsDB auf SQLite umzustellen. Leider (?) habe ich Kritik von hier wegen unsäglich langer Taellennamen befolgt und zu diesem Zweck ein neues Datenbankmodell, analog dem mit MySQL benützten, aber mit deutlich gekürzten Tabellennamen erstellt.
Das heisst, ich kann obige Klasse nicht verwenden, sondern muss eine neue, grundsätzlich gleich aufgebaute Klasse erstellen - ein e Anforderung an die Klasse ist, dass deren Felder und Propertys so heissen, wie die Tabellen, deren Inhalte sie übernehmen sollen. Allees andere gäbe früher oder später ein unentwirrbares Chaos.
Die neue, noch nicht fertig erstellte Klasse mit den neuen Tabellennamen:
Delphi-Quellcode:
type
  TCMQueryClass = Class(TPersistent)
  private
    FidBild: Integer;                                      //    FidBild: Integer;
    FThumbnail : TMemorystream;                            //    FThumbnail : TMemorystream;
    FBitmap: TMemorystream;                                //    FBitmap: TMemorystream;
    FTblBildText : TTblBildText;                           //    FBildDescribeTabelle : TBildDescribeTabelle;
    FTblAlbum : TTbl_Album;                                //    FKategoryTabelle : TKategoryTabelle;
    FPass: String;                                         //    FPass: String;
    FUser: String;                                         //    FUser: String;
    procedure SetBitmap(Value: TMemoryStream);             //    procedure SetBitmap(Value: TMemoryStream);
    procedure SetThumbnail(Value: TMemoryStream);          //    procedure SetThumbnail(Value: TMemoryStream);

    function GetBitmap: TMemoryStream;
    function GetThumbnail: TMemoryStream;
    function FillThumbnail(var Thumbnail: TMemoryStream): TMemoryStream;

  public
    constructor Create(AOwner: TComponent); // override;
    destructor Destroy; override;
    procedure Assign(Source:TPersistent); override;
    property IdBild :integer read FidBild write FidBild;
    property Thumbnail: TMemoryStream read GetThumbnail write SetThumbnail;
    property Bitmap: TMemoryStream read GetBitmap write SetBitmap;
    property TblBildText : TTblBildText read FTblBildText write FTblBildText;
    property TblAlbum : TTbl_Album read FTblAlbum write FTblAlbum;
  end;
Ich habe mir lange darüber Gedanken gemacht, wie man dieses Problem mit OOP-Miteln lösen könnte, aber bislang keine befriedigende Lösung gefunden.

Hat jemand einen Vorschlag?

Gruss
Delbor

Neutral General 12. Okt 2017 15:03

AW: OOP wirklich nicht möglich?
 
Ich finde es komisch, dass du überhaupt solche "Zwischen-Klassen" für die Daten einer Query machst.
Die "Query-Results" sind die Rows die deine Query zurückgibt.

Die nächst höhere Ebene wäre Objekt einer "richtigen" Klasse für das die Daten bestimmt sind.
z.B. TUser o.ä.

Du könntest zwischen die Query und der eigentlichen Klasse noch eine Art Factory stellen, der du die Query-Daten übergibst und die dir dann auf deinen Wunsch hin das gewünschte/benötigte Objekt erstellt. Intern entweder durch manuelle Zuweisung oder durch RTTI-Magie.

Delbor 12. Okt 2017 16:29

AW: OOP wirklich nicht möglich?
 
Hi Neutral General

Zitat:

Du könntest zwischen die Query und der eigentlichen Klasse noch eine Art Factory stellen, der du die Query-Daten übergibst und die dir dann auf deinen Wunsch hin das gewünschte/benötigte Objekt erstellt. Intern entweder durch manuelle Zuweisung oder durch RTTI-Magie.
Das Hauptproblem ist, ich müsste dazu wohl fast das ganze Programm umstellen - einmal mehr. Das eigentliche Problem sind die Bilder. Die sind je nach Kamera zwischen 10 und 24MB gross. Allerdings nur die Rohdaten. Bitmaps sind dreimal grösser.

Mein Programm lädt die vorhandenen Daten vorerst ohne die Bilder. Die werden nachgeladen, wenn sie zur Bearbeitung benötigt werden. Dabei werden Thumpnails für die Navigation und Bitmaps für die grafische Bildbearbeitung erstellt. Bitmaps und Rohdaten werden anschliessend in einer externen DB auf einem beliebigen Laufwerk abgelegt (und dienen da gewissermassen als Backup).

Ich hab eine Factory eingebaut, die mir einen da registrierten Frame (ich habe mehrere verschiedene) zur Laufzeit erstellt und zurückliefert. So, wie ich das verstehe, geht dein Vorschlag genau dahin.
Bei dieser Framefactory sind diverse Frames registriert, die von meiner Anwendung in Abhängigkeit von dem, was der User tun will, alle gebraucht werden und deren Aufbau wegen der Bindung an dieses Programm in den Grundzügen gleich ist.
Das muss aber bei dem angestrebten Objekt nicht sein - oder wird in den wenigsten Fällen so sein.
Die gezeigte Klasse enthält weitere Klassen, die ihrerseits die Tabellen- und Feldstruktur einer DB-Tabelle abbilden.

Gruss
Delbor

TigerLilly 12. Okt 2017 17:40

AW: OOP wirklich nicht möglich?
 
Hilft dir das Decorator Pattern hier nicht?
Du routest aus der neuen Klasse alles auf die alte durch. So kannst du nach und nach alles auf die neue Klasse umstellen.

Delbor 12. Okt 2017 18:23

AW: OOP wirklich nicht möglich?
 
Hi Tigerlilly

Ich muss gestehen, von Decorator Pattern hab ich jetzt von dir zum ersten Mal gehört. Aber nachdem, was mir Wikipedia verrät, wohl eher nicht.
Wenn du die von mir geposteten Klassen ansiehst, wirst du bemerken: Es handelt sich eigentlich um eine in einer Klassenstruktur zusammengefasste Ansammlung sprechender Variablen. Eine Klasse ist das einer Objektliste wegen, die auf diese Weise die Query-Ergebnisse bereithält, ohne das eine Query-Komponente während einer möglicherweise längeren DB-Sitzung dauernd offen sein müsste.

Gruss
Delbor

hoika 12. Okt 2017 19:25

AW: OOP wirklich nicht möglich?
 
Hallo,
Zitat:

aber mit deutlich gekürzten Tabellennamen erstellt.
Das heisst, ich kann obige Klasse nicht verwenden
Was hat denn der Tabellenname mit einer Datenklasse zu tun?

Delbor 12. Okt 2017 23:27

AW: OOP wirklich nicht möglich?
 
Hi hoika
Zitat:

Was hat denn der Tabellenname mit einer Datenklasse zu tun?
In diesem (und nur in diesem) Fall sehr viel. Lies dazu nochmal den Beitrag direkt vor deinem.
Delphi-Quellcode:
type
  TQueryResultClass = Class(TPersistent)
  private
    FidBild: Integer;
    FThumbnail : TMemorystream;
    FBitmap: TMemorystream;
    FBildDescribeTabelle : TBildDescribeTabelle;
    FKategoryTabelle : TKategoryTabelle;
...
  • FIdBild dürfte selbsterklärend sein - das Feld soll den PrimaryKey speicern.
  • FThumbnail ist ein Feld zur Aufnahme von Blob-Daten.
  • Ebenso FBitmap. Aber dann wirds interessant:
  • FBildDescribeTabelle ist vom Tip TBildDescribeTabelle, einer Klasse, die die gleichnamige Tabelle in der DB abbildet und eine Detailtabelle der BildTabelle darstellt. (1:n-Beziehung)
  • TKategoryTabelle hingegen bildet die gleichnamige Tabelle aus der DB ab, ist aber it der Bildtabelle in einer n:m-Beziehung.

Gruss
Delbor

TiGü 13. Okt 2017 08:36

AW: OOP wirklich nicht möglich?
 
Zitat:

Zitat von Delbor (Beitrag 1383183)
Ich habe mir lange darüber Gedanken gemacht, wie man dieses Problem mit OOP-Miteln lösen könnte, aber bislang keine befriedigende Lösung gefunden.

Hat jemand einen Vorschlag?

Wie wär's mit diesem neuartigen Konzept:
Gemeinsamkeiten von zwei oder mehr Klassen in eine gemeinsame Basisklasse und nur in den Ableitungen das neue Zeug?! 8-)

Delphi-Quellcode:
type
  TCustomQuery = Class(TPersistent)
  protected
    FidBild: Integer;
    FThumbnail: TMemoryStream;
    FBitmap: TMemoryStream;
    FPass: String;
    FUser: string;
    procedure SetBitmap(Value: TMemoryStream);
    procedure SetThumbnail(Value: TMemoryStream);
    function GetBitmap: TMemoryStream;
    function GetThumbnail: TMemoryStream;
    function FillThumbnail(var Thumbnail: TMemoryStream): TMemoryStream;
  public
    constructor Create(AOwner: TComponent);
    destructor Destroy; override;
    procedure Assign(Source: TPersistent); override;
    property IdBild: Integer read FidBild write FidBild;
    property Thumbnail: TMemoryStream read GetThumbnail write SetThumbnail;
    property Bitmap: TMemoryStream read GetBitmap write SetBitmap;
  end

  TQueryResultClass = Class(TCustomQuery)
  protected
    FBildDescribeTabelle: TBildDescribeTabelle;
    FKategoryTabelle: TKategoryTabelle;
  public
    procedure Assign(Source: TPersistent); override;
    property BildDescribeTabelle: TBildDescribeTabelle read FBildDescribeTabelle write FBildDescribeTabelle;
    property KategoryTabelle: TKategoryTabelle read FKategoryTabelle write FKategoryTabelle;
  end;

  TCMQueryClass = Class(TCustomQuery)
  protected
    FTblBildText: TTblBildText;
    FTblAlbum: TTbl_Album;
  public
    procedure Assign(Source: TPersistent); override;
    property TblBildText: TTblBildText read FTblBildText write FTblBildText;
    property TblAlbum: TTbl_Album read FTblAlbum write FTblAlbum;
  end;

Elrond 13. Okt 2017 09:18

AW: OOP wirklich nicht möglich?
 
Ich muss zugeben das ich das Problem überhaupt nicht verstehe, was genau möchtest du?

Wenn es dir um eine detailgetreue Abbildung deines Datenbankmodells auf eine Klasse geht, kannst du natürlich einen Wrapper schreiben der aus der DB die entsprechenden Klassen extrahiert. Das lässt sich auch gut automatisiert in den Entwicklungsprozess einbinden. Man kann auch den eher üblicheren Weg gehen und deine Klassen auf die DB Mappen (ORM). In beiden Fällen müsstest du nur an einer Stelle Änderungen vornehmen (Klasse oder DB, statt Klasse und Query). Für deine großen Bilddateien könntest du damit auch das lazy loading Feature nutzen.

generic 13. Okt 2017 12:21

AW: OOP wirklich nicht möglich?
 
Ich kann bei solchen Architektur Problemen diese Buch empfehlen:
https://martinfowler.com/books/eaa.html

Delbor 13. Okt 2017 13:02

AW: OOP wirklich nicht möglich?
 
Hi zusammen
@Elrond
Zitat:

Ich muss zugeben das ich das Problem überhaupt nicht verstehe, was genau möchtest du?
TQueryresultClass ist die Elternklasse; sie bildet die Haupttabelle "BildTabelle" (oder neu und kürzer tbl_bild) ab. Diese in der DB vorhandene "BildTabelle" verfügt über die DB-Felder "BildId", "Thumbnail", "Bitmap" und enthielt ursprünglich auch mal ein Feld "NEF" für die Rohbilddaten. Entsprechend enthält TQueryresultClass private Felder, die diesen Tabellenfeld-Namen entsprechen.
Weiter enthält TQueryresultClass Felder weiterer Unterklassen, welche wiederum uber Felder verfügen, die die von ihnen beschriebenen Detailtabelle repräsentieren.
Das ist im Grunde nichts weiter als eine in klassenform organisierte Menge an Variablen bestimmten Typs( Integer, Strind, Blob.

Nun stelle ich mein Programm von MySQL auf SQLite um; gleichzeitig aber habe ich der Datenbank kürzere, aber trotzdem beschreibende Tabellen- und Feldnamen verpasst. Das heisst nichts anderes, als dass ich jetzt eine Klasse bauen darf, die dasselbe wie TQueryresultClass tut, deren Felder, Propertys und Methoden aber anders heissen. So ist zum Beispiel der Name "BilddescribeTabelle" durch "TblBildText" ersetzt worden.
Da TQueryresultClass gerade mal 2 Vorfahren hat (TObject und TPersistent), kann ich nicht einfach eine neue Klasse ableiten und da neue Felder, Propertys und Methoden einführen, sondern muss einen in den Funktionen soweit identischen Nachbau erstellen.

@TiGü
Ja, das wäre eine Möglichkeit, bzw. das ist der normale Weg der Vererbung. Warum nur hatte ich beim ersten Blick das Gefühl, ich würde es genau umgekehrt machen? Offenbar aber lag genau da mein gordischer Knoten.
Bei nochmaligem durchlesen wird klar: TCustomQuery enthält die Felder(...), die sich in beiden Nachkommen nicht ändern. Die andern werden in den betreffenden Nachkommen neu eingeführt.
Apropos Gordischer Knoten: Ein Hintergedanke war auch, eine Vorfahrklasse zu erstellen, die letztlich an solche "TQueryresult-Klassen" beliebiger Struktur vererben kann, so dass darin Daten aus Tabellen beliebigen Aufbaus gespeichert werden könnten, wie zB. aus einer Adressdatenbank oder sonstwas.

Gruss
Delbor

stahli 13. Okt 2017 16:58

AW: OOP wirklich nicht möglich?
 
Mir geht es wie Elrond.

Ich würde die Logik eher in verschiedene Ebenen aufteilen.

Deine Buinessklassen sollten die Daten so verwalten und die Felder so benennen, wie es für die Geschäftslogik Sinn macht.

Dann müsstest Du Deinen Klassen die Fähigkeit geben, sich in eine Datenbank zu speichern und die eignen Daten daraus wieder zu laden.
Da spielt dann für die Businessklassen keine Rolle, was da für eine Datenbank genutzt wird.

Welche Datenbank genutzt wird und wie die Statements dazu aussehen, könnte in austauschbaren Units oder Komponenten geregelt werden.
Dann kannst Du zunächst MyUnit_MySql verwenden und dort alle bisherigen Funktionen deklarieren und dann diese durch MyUnit_SQLite ersetzen.
Der Interface-Abschnitt müsste die selben Methoden veröffentlichen aber im Implementations-Abschnitt wären diese jeweils für eine andere Datenbank umgesetzt.

Falls Du Dich mit Interfaces auskennst oder Dich damit befassen willst, wären diese eigentlich prädestiniert für solche Anwendungsfälle.

In jedem Fall ist es sinnvoll, die Zuständigkeiten wie Geschäftslogik zum einen und Datenbankverbindung zum anderen möglichst entkoppelt voneinander zu regeln. Für eine detaillierte Umsetzung gibt es dann wieder mehrere Möglichkeiten.

Elrond 13. Okt 2017 18:27

AW: OOP wirklich nicht möglich?
 
Zitat:

Zitat von Delbor (Beitrag 1383241)
Nun stelle ich mein Programm von MySQL auf SQLite um; gleichzeitig aber habe ich der Datenbank kürzere, aber trotzdem beschreibende Tabellen- und Feldnamen verpasst. Das heisst nichts anderes, als dass ich jetzt eine Klasse bauen darf, die dasselbe wie TQueryresultClass tut, deren Felder, Propertys und Methoden aber anders heissen. So ist zum Beispiel der Name "BilddescribeTabelle" durch "TblBildText" ersetzt worden.

Wenn dies dein Hauptproblem darstellt, dann wäre die Verwendung eines DB Wrappers die sauberste Lösung. Dieser Wrapper generiert dir automatisch aus deiner Datenbank die passenden Delphiklassen. Dieser Ansatz ist etwas ungewöhnlich im vergleich zum klassischen Object-Relational Mapping ala Hibbernate, funktioniert jedoch genauso gut. Somit müsstest du nur deinen Wrapper beim wechseln der Datenbank anpassen.

Delbor 14. Okt 2017 09:39

AW: OOP wirklich nicht möglich?
 
Hi Elrond

Meinst du sowas?

Gruss
Delbor

TigerLilly 14. Okt 2017 09:46

AW: OOP wirklich nicht möglich?
 
Das sieht so aus, als ob du für einen Fehler, den du gemacht hast (oder etwas, das du jetzt einfach besser weißt), einen eleganten Workaround suchst. Mir kommt diese Lösung sehr kompliziert und schwerfällig vor.

Kannst du nicht alles kübeln + neu beginnen? Und diesmal von Beginn an besser?

Oder wenigstens mit suchen/ersetzen alles auf den gleichen Stand bringen, den du gerne hättest?

Delbor 14. Okt 2017 12:19

AW: OOP wirklich nicht möglich?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hi Tigerlilly

Zitat:

Das sieht so aus, als ob du für einen Fehler, den du gemacht hast (oder etwas, das du jetzt einfach besser weißt), einen eleganten Workaround suchst. Mir kommt diese Lösung sehr kompliziert und schwerfällig vor.
Das ist nicht ganz falsch. Zu Anfang erstellte ich mir in MySQL-Workbench ein Datenbankmodell. Dabei ergaben sich, zB. in einer n:m-Zwischentabelle, so unsägliche Namen wie "BildDescribeTabelle_Bildtabelle_idBild" für ein Fremdschlüsselfeld. Auch wenn ich das relativ früh bemerkte, wollte mir vorerst keine gangbare Lösung einfallen - etwa Text statt Describe oder tbl statt Tabelle für einen immer wieder auftauchenden Teilstring.

Schwerfällig ist eigentlich der Part, der sich, ob unschön oder nicht, zuallererst anbietet: Ich schreibe eine neue Klasse, die genau das tut, was die alte macht, aber mit Feldern und Methodenköpfen analog der SQLite-Datenbank. Nicht unmöglich, aber eine Sisiphusarbeit - einmal ein klein wenig nicht aufgepasst, und schon beginnt später die Suche nach der Nadel im Heuhaufen.
Wobei das nicht mal das Hauptproblem ist. Es wäre aber wünschenswert, eine Customklasse zu haben, um, sollte ich in Zukunft vor dem selben Problem stehen, dieses mit OOP lösen zu können.
Zitat:

Kannst du nicht alles kübeln + neu beginnen? Und diesmal von Beginn an besser?
Eher nicht. Zum einen würde dies bedeuten, ein funktionierendes Programm neu zu bauen anzufangen und zum andern wäre die Sache mit der Klasse TQueryresultClass nicht gelöst - es sei denn, du meinst mit 'kübeln' gerade mal TQueryresultClass. Das hiesse dann: da weitermachen, wo ich jetzt stehe, nämlich beim Neuerstellen von TQueryresultClass unter Verwendung der nun aktuellen Namen.

Aber vielleicht ist das noch viel einfacher, als ich mir gedacht habe: TQueryresultClass erhält ein Feld "Params" und feuert einen Event - das Datenmodul, oder wer auch immer, erhält einen Eventhandler, der die Tabellen und Felder der DB abfragt. Soweit, so gut: wie TQueryresultClass in die Strings der Params-Liste die Ergebnisse des Querys speichert, weiss ich erstmal noch nicht könnte so geschehen.

Andrerseits ist das bis jetzt auch nur die Halbe Wahrheit - TQueryresultClass besitzt Unterklassen, die auch umbenannt werden müssten...


Obigen Absatz habe ich mal stehen lassen. Durchgestrichen habe ich ihn wegen der Unterklassen, die auf diese Weise nicht deklariert werden können, Zumindest so, wie ich das bis anhin sehe.

Im Anhang lege ich mal die TQueryresultClass-Unit bei, um den Aufbau der Klasse zu verdeutlichen.

Gruss
Delbor

Elrond 16. Okt 2017 07:31

AW: OOP wirklich nicht möglich?
 
Zitat:

Zitat von Delbor (Beitrag 1383315)
Hi Elrond

Meinst du sowas?

Gruss
Delbor


Nein das ist einfach nur ein Wrapper um eine DB DLL.
Ich meine einen Wrapper der dir aus deinen Datenbankmodell die passenden Delphiklassen generiert, z.B. wird aus Tabelle A mit Spalten AA und AB eine Klasse A mit den properties AA und AB.
Natürlich kümmert sich der Wrapper auch darum deine Objekte zu persistieren und wieder aus der Datenbank zuladen. Der entscheidende Unterschied zu den gängigen OR Mappern besteht darin das du zuerst deine Datenbank beschreibst und nicht deine Klassen.

mkinzler 16. Okt 2017 07:36

AW: OOP wirklich nicht möglich?
 
Das unterstützen die gängigen OR-Mapper eigentlich auch.

Delbor 18. Okt 2017 09:20

AW: OOP wirklich nicht möglich?
 
Hi zusammen

Inzwischen habe ich, wie vor einiger Zeit von TigerLilly angesprochen, quasi "von Hand", bzw. durch Suchen und ersetzen, eine neue Klasse analog TQueryresultClass erstellt. Soweit wär eigentlicheine Vorausetzung für den Umbau gegeben.
Trotzdem hat mich die Sache nicht losgelassen, und so hab ich auch das gefunden. Weiter habe ich mir auch die verschiedenen Beiträge und Tutorials zu Interfaces hier und auf andern Webseiten, insbesondere Stahlisoft und Youtube, angesehen.
Die OOP-Vorgehensweise nach dem Decorator Pattern wäre demnach also gewesen:
  1. Ein Interface zu deklarieren(TInterfacedobjekt und (muss ich nochmal nachsehen)
  2. TQueryresultClass nicht nur von TPersistent, sondern zusätzlich von meinem Interface abzuleiten
Noch nicht ganz klar ist, ob ich die Methoden, die meine neu benannten Tabellen auslesen, im Interface nur deklariere oder auch implementiere ( Die Embarcadero Onlinehelp irritiert mich da etwas).
So aus dem Stegreif heraus: Ja zu letzterem und anschliessend in TQueryresultClass nur deklarieren, damit ich sie von da auch ausrufen kann. Diese Frage wird mir letztlich endgültig klar, wenn ich mir ein Testprogrämmchen geschrieben habe.

Gruss
Delbor

TigerLilly 18. Okt 2017 09:41

AW: OOP wirklich nicht möglich?
 
Fein, dass mein Vorschlag des Decorator Patterns wieder auftaucht. *eiteldreinschau*

Aber der decorator macht ja eigentlich was anderes, als du hier beschrieben hast:Ein decorator legt eine andere Benutzerschicht über deine Klasse und reicht so Funktionalitäten durch. So kannst du die Methoden des Decorators aufrufen und der routet das weiter an die alte Klasse. das ist eine recht elegante Methode, wenn man wie du ja auch - Klassen umbaut und beide Varianten aktiv halten möchte.

Interfaces sind cool, haben mit dem Entwurfsmuster aber nichts zu tun. Interfaces sind operativ (=wie mache ich es), Entwurfsmuster sind taktisch(=was mache ich?). Und eine Strategie (=warum mache ich das?) braucht es natürlich auch.

DeddyH 18. Okt 2017 09:50

AW: OOP wirklich nicht möglich?
 
Man kann in einer Interface-Deklaration überhaupt nichts implementieren, das ist ja gerade deren Sinn. Wenn Du von außen auf ein Interface zugreifst, weißt Du zunächst einmal nur, dass die dahinterliegende Objektinstanz garantiert alle Eigenschaften besitzt und Methoden implementiert, die im Interface vereinbart sind.

stahli 18. Okt 2017 12:18

AW: OOP wirklich nicht möglich?
 
@Delbor

Ich denke, Du musst Deinen Grundsatzüberlegungen nochmal etwas ordnen und strukturieren.

Nimm Dir mal ein Blatt Papier und zeichne Dir mal einen Plan, wo welche Zuständigkeiten geregelt werden sollen. Je klarer Dir das gelingt, je strukturierter wird Dein Programm aufgebaut sein.

Für Außenstehende ist vermutlich schwer nachzuvollziehen, was Du aktuell genau vorliegen hast und was Du ändern willst.

Z.B. ist m.E. bisher nicht klar geworden, wie diese Komponenten
Delphi-Quellcode:
    FTblBildText : TTblBildText; // FBildDescribeTabelle : TBildDescribeTabelle;
     FTblAlbum : TTbl_Album; // FKategoryTabelle : TKategoryTabelle;
aufgebaut sind. Haben sie noch eine Verbindung zur Datenbank oder nicht?
Wenn nicht, warum sind es dann unterschiedliche Klassen und warum sind die Felder unterschiedlich benannt?
Wenn ja, warum nimmst Du nicht eine Klasse, die die Daten unabhängig von einer Datenbank verwaltet und bearbeitet?

Wie viele Daten verwaltest Du insgesamt in der Anwendung? Können alle Daten insgesamt im Speicher gehalten werden oder ist die Datenbank so groß, dass immer nur bestimmte Datensätze daraus abgeholt werden können?

Das sind viele grundsätzliche Fragen, die eigentlich erst mal geklärt werden müssten und zu entsprechend unterschiedlichen Lösungen führen werden.

Wie in #12 schon mal angesprochen, musst Du Dir eine übersichtliche Struktur überlegen, die klare Zuständigkeiten und Verbindungen verschiedener Projektmodule abbildet.

Dafür gibt es dann je nach den Gegebenheiten verschiedene Lösungsmöglichkeiten.


Was ich generell nicht verstehe ist, dass Du Deine Businessklassenstruktur änderst, wenn Du die Datenbank wechselst oder die Tabellen in der Datenbank andere Namen haben.

Nach meiner Überlegung müsstest Du in Deinem Projekt das "Datenbankmodul" anpassen, aber nicht die Businessklassen.


Interfaces zu verwenden kann sinnvoll sein, muss es aber nicht in jedem Fall. Wenn Du damit noch keine Erfahrungen hast, würde ich die Projektstruktur erst mal überarbeiten und auf Interfaces verzichten. Da hängt noch einiges an notwendigen Anpassungen dran, die jetzt vielleicht unnötig verwirren würden.

Statt dessen solltest Du eine Datenbankunit oder Datenbankklasse einführen, die alle Zugriffe auf die alte Datenbank kapselt - so dass Deine Anwendung nicht mehr die Datenbank selbst kennt, sondern nur noch Deine Datenbankunit oder Datenbankklasse.
Dann kannst Du eine neue Datenbankunit oder -Klasse aufbauen, die die selben Schnittstellen nach außen hat, aber intern auf eine andere Datenbank geht.

So hättest Du schon mal eine gute Trennung der Zuständigkeiten.

Delbor 18. Okt 2017 16:10

AW: OOP wirklich nicht möglich?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hi zusammen

@ stahli
Zitat:

FTblBildText : TTblBildText; // FBildDescribeTabelle : TBildDescribeTabelle;
FTblAlbum : TTbl_Album; // FKategoryTabelle : TKategoryTabelle;
Grundsätzlich trennt mein Programm GUI und Datenhaltung. Letzteres fällt in die Zuständigkeit zweier Datenmodule; je eines für eine Verbindung zu MySQL und eines, relativ neu in Arbeit, zu SQLite.
Die Datenbank selbst beinhaltet 12 Tabellen, wovon zurzeit nur 4 für Bild- und Textinhalte zuständig sind. Von drei dieser Tabellen gibt es innerhalb von TQueryresultClass Entsprechungen gleichnamiger Klassen.
Das obige Zitat gibt 2 Klassenfelder meiner gleichaufgebauten Klassen TQueryCMresultClass(FTblBildText ,FTblAlbum) und TQueryresultClass(FBildDescribeTabelle,FKategoryTa belle) wider.
Ich hänge hier mal noch TQueryCMresultClass an, um die Unterschiede zu verdeutlichen; diese bestehen ausschlieslich aus den in TQueryCMresultClass verwendeten kürzeren Tabellen- und Feldbezeichnern.
Natürlich wurde die SQLite_Datenbank auch mit den verkürzten Tabellennamen erstellt.
Zitat:

Haben sie noch eine Verbindung zur Datenbank oder nicht?
Wie bereits gesagt, handelt es sich hierbei um Klassenfelder, bzw. Unterklassen, die selbst keine Verbindung zur Datenbank haben.
Bei Select-Abfragen der TQuery-Komponente werden deren Resultate jeweils pro Datensatz über 3 Tabellen (ohne Zwischentabelle) in einer TQueryCMresultClass - Instanz festgehalten und diese einer Objectlist hinzugefügt. Dabei gibt es eine Besonderheit: Bilddaten werden bei der ursprünglichen Abfrage über mehrere Datensätze mit Ausnahme des Thumbnails vorerst nicht abgefragt, sondern erst, wenn sie zur Darstellung des Bildes benötigt werden.

Ein Ziel meiner Umstellung auf SQLite: von mir so genannte "Satelliten-DBs" zu erstellen(*). Das hat folgende Grund: Zur Zeit speichere ich die Bilder noch in einem Ordner auf Festplatte. Wenn die Dinger schliesslich in die DB geschrieben werden, werden aus den Rohbildern nicht nur die Thumbnails, sondern auch gleichzeitig Bitmaps in Originalgrösse erstellt. Dabei sollten auch die Rohdaten ursprünglich mit in die DB - und blähen diese zu gigantischer Grösse auf.
Und hier kommen dann meine "Satelliten-DBs" zum Zug: In ihnen werden die Rohdaten und Originalbitmaps zusammen mit einem Textfeld für den Namen und einem FK-Integer für das Album gespeichert. Diese Dinger bringen es auf Grössen von in etwa 25GB oder weniger, sind also eigentlich sogar als eine Art Rohdatenbackup zu gebrauchen und können Extern an beliebigen Orten gespeichert werden.
Wo sich diese "Satelliten-DBs" befinden, bestimmt letzlich der User über ein Optionen-Fenster.

Zitat:

Wie viele Daten verwaltest Du insgesamt in der Anwendung? Können alle Daten insgesamt im Speicher gehalten werden oder ist die Datenbank so groß, dass immer nur bestimmte Datensätze daraus abgeholt werden können?
Als ich das letzte mal gezählt habe, waren es ca. 13 000 Fotos.
Zitat:

Was ich generell nicht verstehe ist, dass Du Deine Businessklassenstruktur änderst, wenn Du die Datenbank wechselst oder die Tabellen in der Datenbank andere Namen haben.
An den Strukturen der Klassen ändert sich ja nichts - nur die verwendeten Namen sind kürzer geworden. Am Beispiel eines SQL-Statements:
Delphi-Quellcode:
function TFDMySQLDml.DefineBildSQL3(Kath_Id: Integer) : String;
begin
Result := 'SELECT Bildtabelle.idBild as BildID, ' +
            'bilddescribetabelle.BilddesribeID as BildDescribeId, ' +
            'bilddescribetabelle.bildkatID as BildkatID, '+
            'bilddescribetabelle.bildname as Bildname, ' +
            'bilddescribetabelle.bildbeschreibung as Bildbeschreibung, '+
            'bilddescribetabelle.bildlegende as bildlegende, ' +
            'kategorien_tabelle.Kath_ID as KathID, ' +
            'kategorien_tabelle.Kategorie as Kategorie, '+
            'kategorien_tabelle_has_bilddescribetabelle.kategorien_tabelle_Kath_ID as TblKat_Id, '+
            'kategorien_tabelle_has_bilddescribetabelle.BildDescribeTabelle_BilddesribeID as BildDesc_Id '+
          'FROM ' +
            'bildtabelle, bilddescribetabelle, ' +
            'kategorien_tabelle_has_bilddescribetabelle, ' +
            'kategorien_tabelle '+
          'WHERE '+  {erste Tabelle}
            'Kategorien_tabelle.Kath_Id = :Kath_Id '+
          'AND '+   {zweite (Selektionstabelle) Tabelle wird mit erster verglichen}
            'kategorien_tabelle_has_bilddescribetabelle.kategorien_tabelle_Kath_ID = Kategorien_tabelle.Kath_Id ' +
          'AND '+                                                                         //--------------------
            'kategorien_tabelle_has_bilddescribetabelle.BildDescribeTabelle_BilddesribeID = bilddescribetabelle.BilddesribeID ' +
          'AND '+
            'Bildtabelle.idBild = bilddescribetabelle.bildtabelle_idbild';
//  Showmessage(Result);
end;
Das dürfte viel schwerer zu lesen sein als:
Delphi-Quellcode:
function TFDMySQLDml.DefineBildSQL3(Kath_Id: Integer) : String;
begin
Result := 'SELECT Tbl_Bild.idBild as BildID, ' +
            'TblBildText.BilddesribeID as BildDescribeId, ' +
            'TblBildText.bildkatID as BildkatID, '+
            'TblBildText.bildname as Bildname, ' +
            'TblBildText.bildbeschreibung as Bildbeschreibung, '+
            'TblBildText.bildlegende as bildlegende, ' +
            'TblAlbum.Kath_ID as AlbumId, ' +
            'TblAlbum.Kategorie as Kategorie, '+
            'TblAlbum_has_TblBildText.TblAlbum_Album_Id as TblKat_Id, '+
            'TblAlbum_has_TblBildText.TblBildText_BilddesribeID as BildDesc_Id '+
          'FROM ' +
            'bildtabelle, TblBildText, ' +
            'TblAlbum_has_TblBildText, ' +
            'TblAlbum '+
          'WHERE '+  {erste Tabelle}
            'TblAlbum.Album_Id = :Album_Id '+
          'AND '+   {zweite (Selektionstabelle) Tabelle wird mit erster verglichen}
            'TblAlbum_has_TblBildText.TblAlbum_Album_Id = TblAlbum.Album_Id ' +
          'AND '+                                                                         //--------------------
            'TblAlbum_has_TblBildText.TblBildText_BilddesribeID = TblBildText.BilddesribeID ' +
          'AND '+
            'Bildtabelle.idBild = TblBildText.bildtabelle_idbild';
//  Showmessage(Result);
end;
Wobei ich jetzt nur mal einiges ersetzen lassen habe - zu Demozwecken in einer neuangelegten, aber nicht gespeicherten Unit.
Zitat:

Nach meiner Überlegung müsstest Du in Deinem Projekt das "Datenbankmodul" anpassen, aber nicht die Businessklassen.
Hmmm - was verstehst du genau unter Businessklassen? In der Schichtenprogrammierung gibt es meines Wissens folgende Ebenen//Schichten:
  1. Die Gui
  2. Die Logik- oder BusinessSchicht und schliesslich die
  3. Datenschicht (Datenmodul(e), DB-Verbindungen)
wobei die oberen nach unten durchgreifen dürfen, aber nicht umgekehrt. Wobei die Daten von der Gui in einem Stringgrid dargestellt werden und das daher auf die Objectliste mit meinen Datenklassen zugreifen muss - das aber entspricht dem Modell(von oben nach unten).

Zitat:

Interfaces zu verwenden kann sinnvoll sein, muss es aber nicht in jedem Fall. Wenn Du damit noch keine Erfahrungen hast, würde ich die Projektstruktur erst mal überarbeiten und auf Interfaces verzichten. Da hängt noch einiges an notwendigen Anpassungen dran, die jetzt vielleicht unnötig verwirren würden.
Deshalb habe ich TQueryCMresultClass zuerst mal analog TQueryresultClass "händisch erstellt, indem ich TQueryresultClass als TQueryCMresultClass abgespeichert und verschiedene Namen durch <Suchen-Ersetzen> geändert habe.
Für Interfaces gibts erstmal ein (umfangreiches?) Testprogramm. Und erst Sachen, die da funktionieren und mir klar ist, warum sie das tun, können in meinem Programm eingebaut werden.

Zitat:

Statt dessen solltest Du eine Datenbankunit oder Datenbankklasse einführen, die alle Zugriffe auf die alte Datenbank kapselt - so dass Deine Anwendung nicht mehr die Datenbank selbst kennt, sondern nur noch Deine Datenbankunit oder Datenbankklasse.
Dann kannst Du eine neue Datenbankunit oder -Klasse aufbauen, die die selben Schnittstellen nach außen hat, aber intern auf eine andere Datenbank geht.
Das sind meine Datenmodule.

(*) abgesehen davon, dass es eigentlich unsinnig ist, als Anwendungsdatenbank einen Server wie MySQL zu verwenden (Ausnahme:EmbeddedServer)

Gruss
Delbor

MichaelT 18. Okt 2017 17:39

AW: OOP wirklich nicht möglich?
 
Mit OOP Mitteln ist interessante Frage.

In einem Satz formuliert. Ist dein Problem, dass du in der TCMQueryClass SQL Statements drinnen hast?

Deine gecashten Objekte schreiben können sich selbst in DB zurückschreiben, etwas platt formuliert ala object.SetDirty und schon rasseln die SQL Statements.

Jetzt hast du eine Applikation die auf beide DBs gleichzeitg zugreift? Soll das so bleiben?

Oder du willst in Zukunft willst du nurmehr auf SQLite gehen oder Wahlweise entweder auf MySQL XOR MySQLite zugreifen?

Falls nur eine Datenbank in Zukunft gefragt ist migriere den Datenbestand.

---

Dein Problem anders definiert heißt der SQL String passt nicht zur DB. In dem Fall bietet sich als billige Variante an die Formatierung des String herauszulösen sofern die Logik für den Zugriff abseits der Basisdatentypen halbwegs indent ist.

Was du brauchst ist eine normiertes SQL. Am besten ist du machst eine 'globale' Variable in der die ZielDB drinnensteht und eine Funktion FormatSQL('SQLStatement....) : String, welche in Abhängigkeit der Ziel DB das richtige SQL zurückgibt.

Die Funktion kann dann die Methode einer Klasse genauso sein und ab dann kannst dahinter rumfuhrwerken wie du willst. Du wirst nicht umhinkommen den Code um zumindest das Klammern der SQL Statements zu erweitern.

SQL.Format(SELECT_VON_TABELLE);

So etwas in die Richtung oder FormatSQL(SELECT_VON_TABELLE) ist an sich pragmatischer.

Konstanten als 'Symbol' für die SQL Statements wirst du leider einführen müssen oder du verwendest String Variablen ... In dem Punkt kann man sich schon ausleben. Wesentlich ist die Behandlung der SQL Statements auf einer 'höheren' Ebene.

Die Prototypen für Formatierung und alles was dazu gehört lagerst du in eine Basisklasse aus. Auf dem Weg geht nicht mehr viel schief. Du kannst die Konstanten in der Klasse definieren usw...

Wenn die Programmlogik sich mit dem Lesen der Blobs massiv ändert müssten wir schauen. Billiger geht es nicht mehr.

Wo du die globale Variable für den Ziel DB Typ ansiedelst kann ich dir jetzt nicht sagen.

stahli 18. Okt 2017 19:07

AW: OOP wirklich nicht möglich?
 
@Delbor

Das, was Du beschreibst, klingt schon ganz gut, aber Dein Quelltext lässt das nicht ganz erkennen.

Du könntest es Dir deutlich einfacher machen, aber müsstest dafür sicher Dein Projekt neu aufbauen.
Mal zwei Ansätze.

Empfehlenswert wäre m.E. folgender neuer Ansatz (mal am Beispiel Personen und Autos):

Du baust 2 Klassen:

Delphi-Quellcode:
TPerson = class
  Vornme: string;
  Nachname: string;
  Autos: TList<TAuto>;
  Kilometer: Integer;
end;

TAuto = class
  Typ: string;
  Farbe: string;
  Fahrer: List<TPerson>;
  Kilometer: Integer;
  procedure Fahren(aFahrer: TPerson; aKM: Integer);
end;
Jetzt kannst Du Objekte erzeugen, im Speicher verwalten, in Listen zuordnen und Kilometer für Fahrer und Autos zählen.

Das sind die Businessklassen. Nur das brauchst Du, um die Kilometer für die Fahrer und Autos zu ermitteln.

Jetzt kannst Du schon mal testweise per Code einige Testpersonen und Autos anlegen und einige Strecken fahren.
Also zumindest kannst Du schon mal ein paar Testobjekte erzeugen.

Nun baust Du die GUI, und Du kannst sehen, ob die Testdaten korrekt angezeigt werden und ob Du über die GUI neue erzeugen kannst.
Bis hierher spielt die Datenbank noch keine Rolle!

Aber jetzt wollen wir die Daten natürlich doch irgendwie speichern.
Also müssen die Objekte irgendwie die Fähigkeit haben, gespeichert und gelesen zu werden.
Wenn es dafür eine einheitliche Regelung gibt ist das sicher sinnvoll.
Daher könnte man eine Basisklasse einführen, die das unterstützt:


Delphi-Quellcode:
TSaveLoad = class
  procedure Save; virtual;
  procedure Load; virtual;
end;

TPerson = class(TSaveLoad)
  Vornme: string;
  Nachname: string;
  Autos: TList<TAuto>;
  Kilometer: Integer;
  procedure Save; override;
  procedure Load; override;
end;

TAuto = class(TSaveLoad)
  Typ: string;
  Farbe: string;
  Fahrer: List<TPerson>;
  Kilometer: Integer;
  procedure Fahren(aFahrer: TPerson; aKM: Integer);
  procedure Save; override;
  procedure Load; override;
end;

Wenn man die Daten einfach mal in einer Ini speichern will, kann man das in den überschriebenen Methoden Save und Load realisieren.
Wenn man dann auf eine Datenbank welchselt und später nochmal auf eine andere, ändert man einfach die Methoden wieder ab.


Eine andere Variante wäre, die Basisklasse und die Methoden Save und Load weg zu lassen und statt dessen einen DBManager zu bauen:
Also man erzeugt eine weitere Klasse

Delphi-Quellcode:
TDBManager = class
  function GetAllPersons_WithoutCars: TList<TPerson>;
  function LoadPerson(aPersonId: Integer): TPerson;
  procedure SavePerson(aPerson: TPerson);
end;
Dann weiß nur der DBManager, wie die Verbindung zu den Datenbanktabellen herzustellen ist.
Man kann dann unterschiedliche BDManager für unterschiedliche Datenbanken erstellen und die jederzeit austauschen.



In ALLEN FÄLLEN sollte aber TPerson.Vorname IMMER TPerson.Vorname heißen, selbst wenn der ersten Datenbank die Personentabelle People und das Feld FirstName und in der neuen Datenbank die Tabelle Leute und das Feld Name_1 heisst.
NUR in den Methoden Save und Load bzw. in der anderen Variante in dem DBManager müssen die Anpassungen an die Datenbankänderungen vorgenommen werden.


Um wieder auf Dein Beispiel zurück zu kommen:
Statt z.B. in der Buinessklasse mit
Delphi-Quellcode:
FTblBild_IdBild: integer;
bzw.
Delphi-Quellcode:
FBildTabelleIdBild: integer;
zu arbeiten, wäre dort
Delphi-Quellcode:
fBildID: integer;
sinnvoll, weil die bei der Businesslogik eben interessiert.
Wie die passenden Daten aus einer Ini-Datei, aus Datenbank A, Datenbank B oder von Google geholt werden, ist für die Buinessklasse selbst und für die Benennung der Properties völlig uninteressant.


Genau das ist ja auch ein Ziel der OOP, das man verhalten kapselt und auch überschreiben kann.


Sorry, falls das etwas chaotisch ist. Aber vielleicht hilft das ja dennoch mal auf einen besser strukturierten Ansatz.

Delbor 19. Okt 2017 11:11

AW: OOP wirklich nicht möglich?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hi zusammen

@MichaelT

Zitat:

In einem Satz formuliert. Ist dein Problem, dass du in der TCMQueryClass SQL Statements drinnen hast?
Nein. Beide Klassen können nur die von einem Query gelieferten Ergebnisse jeweils für einen Datensatz über drei 3 Tabellen speichern.

Zitat:

Deine gecashten Objekte schreiben können sich selbst in DB zurückschreiben, etwas platt formuliert ala object.SetDirty und schon rasseln die SQL Statements.
Diese Klassen sind nicht für Insert-Vorgänge vorgesehen, sondern speichern nur die Ergebnisse von Select-Abfragen. Wobei die Bilder erst dann abgefragt werden, wenn sie dargestellt werden sollen.

Zitat:

Jetzt hast du eine Applikation die auf beide DBs gleichzeitg zugreift? Soll das so bleiben?
Oder du willst in Zukunft willst du nurmehr auf SQLite gehen oder Wahlweise entweder auf MySQL XOR MySQLite zugreifen?
Wenn die Geschichte mit SQLite funktioniert, in diesem Fall nicht mehr. Zu einem (viel?) späteren Zeitpunkt kann es interessant sein, wenn die Anwendung auch auf andere DBMSe zugrreifen kann. Das ist aber vorerst nicht gefragt.
Gleichwohl - die MySQL-DB bleibt vorerst bestehen.

Zitat:

Falls nur eine Datenbank in Zukunft gefragt ist migriere den Datenbestand.
Die Datenbank enthält zur Zeit keine Daten. Und bis zur Fertigstellung des Programms wird sie auch nur Testdaten enthalten.

@stahli:
Zitat:

In ALLEN FÄLLEN sollte aber TPerson.Vorname IMMER TPerson.Vorname heißen, selbst wenn der ersten Datenbank die Personentabelle People und das Feld FirstName und in der neuen Datenbank die Tabelle Leute und das Feld Name_1 heisst.
In diesem Beispiel kann auch ich mit meinen katastrophalen Englischkenntnissen nicht gross irren.
Aber sobald Abkürzungen mit im Spiel sind - zum Bleistift 'Tbl_BildText' als (Teil)-String einer Objekt-Unterkasse und 'BildDesribeTabelle' als Tabellenname in der DB wird mE. die Verwechslungsgefahr grösser und somit auch die Fehleranfälligkeit. Das Objekt oder eine Methode desselben sollten also eher möglichst gleichnamige Bezeichner wie die korrespondierenden Tabellen/Felder in der DB haben.

TTblBildText; ist eine Unterklasse von TQueryCMResultclass(Speichert Ergebnisse aus der SQLite-DB).
TBildDescribeTabelle;ist eine Unterklasse von TQueryResultclass(Speichert Ergebnisse aus der MySQL-DB).

Diese beiden Bezeichner werden sich innerhalb ihrer Klasssen nicht ändern.

Gruss
Delbor

DeddyH 19. Okt 2017 11:17

AW: OOP wirklich nicht möglich?
 
Je mehr ich lese, desto mehr frage ich mich: wozu das Ganze? Soll das ein ORM werden/sein? Dann haben IMO die Tabellen- bzw. Feldnamen in den Objekten nichts zu suchen, die Objekte sollte es nicht interessieren, woher die Daten kommen oder wohin sie gehen, dafür ist eine Schicht zuständig. So wie ich es sehe bringt der ganze Aufwand momentan 0% Nutzen (sofern ich das richtig überblicke, ich kann mich auch irren), sondern sorgt eher für Verwirrung.

stahli 19. Okt 2017 11:49

AW: OOP wirklich nicht möglich?
 
Zitat:

Zitat von Delbor (Beitrag 1383659)
Aber sobald Abkürzungen mit im Spiel sind - zum Bleistift 'Tbl_BildText' als (Teil)-String einer Objekt-Unterkasse und 'BildDesribeTabelle' als Tabellenname in der DB wird mE. die Verwechslungsgefahr grösser und somit auch die Fehleranfälligkeit. Das Objekt oder eine Methode desselben sollten also eher möglichst gleichnamige Bezeichner wie die korrespondierenden Tabellen/Felder in der DB haben.

Das sehe ich ganz gegensätzlich.
Die Probleme, die Du hier hast, kommen m.E. nur daher, dass Du Deine Klassenmember entsprechend Deiner Tabellen benennen willst.
Wie die Tabellen ausssehen und wie die heißen, hat aber die Businessklassen letztlich nicht zu interessieren.

Was Du tust, ist ein neues Projekt mit neuen Klassen aufzubauen, weil Du eine andere Datenbank anbinden willst. Das ist definitiv der falsche (aufwändigste) Ansatz.

Delbor 19. Okt 2017 15:14

AW: OOP wirklich nicht möglich?
 
Hi zusammen

TQueryResultClass arbeitet schon länger zur vollkomenen Zufriedenheit - das Problem, das ich mit dem Threadtitel angesprochen habe, war: Gäbe es eine OOP-Lösung, um TQueryResultClass zu ändern? Änderungsziel: Gleiches Funktionieren, wie ursprünglich implementiert, aber mit andern Bezeichnern. Aber erstmal, was TQueryResultClass tut (und mehr hat diese Klasse auch nicht zu tun):
Delphi-Quellcode:
procedure TFDMySQLDml.SelectBildDaten(FCategoryKey : Integer);              
  var SQLString: String; Kath_Id : integer;
begin
  Kath_Id := FCategoryKey;
  SQLString := DefineBildSQL3(Kath_Id); // Liefert den SQL-String zurück
  FDQueryMain.Connection := Self.FDConnectionMySql;
  FDQueryMain.Close;
  FDQueryMain.SQL.Text := SQLString;
  FDQueryMain.Params.CreateParam(ftInteger, 'Kath_Id', ptOutput);  
  FDQueryMain.Params[0].AsInteger := Kath_Id;
  FDQueryMain.Open;
  FDQueryMain.First;    
  While (Not FDQueryMain.Eof) do                              
  begin
    FQueryResult := TQueryResultClass.Create(Self);
    FQueryResult.IdBild := (FDQueryMain.FieldByName('BildId').AsInteger);
    [QUOTE]FQueryResult.BildDescribeTabelle.BilddesribeID := FDQueryMain.FieldByName('BildDescribeId').AsInteger;[/QUOTE]
    FQueryResult.BildDescribeTabelle.bildkatID := FDQueryMain.FieldByName('bildkatID').AsInteger;
    FQueryResult.BildDescribeTabelle.BildName := FDQueryMain.FieldByName('BildName').AsString;
    FQueryResult.BildDescribeTabelle.BildBeschreibung := FDQueryMain.FieldByName('Bildbeschreibung').AsString;
    FQueryResult.BildDescribeTabelle.BildLegende := FDQueryMain.FieldByName('Bildlegende').AsString;
    FQueryResult.KategoryTabelle.Kategory := FDQueryMain.FieldByName('Kategorie').AsString;
    FDQueryMain.Next;
    FCategoryBildlist.Add(FQueryResult);    // Die Records in TQueryResultClass müssen überprüft werden.
  end;
  FDQueryMain.Close;
end;
Wie ersichtlich ist: Weder das Query noch TQueryResultClass verfügen in dieser Abfrage über einen Stream für Blobdaten. Interesieren tun hier nur gerade mal die Begleitdaten zum Bild.
Das besondere, weswegen es TQueryResultClass überhaupt gibt, sind seine Bilder-Propertis:
Delphi-Quellcode:
    property Thumbnail: TMemoryStream read GetThumbnail write SetThumbnail;
    property Bitmap: TMemoryStream read GetBitmap write SetBitmap;
Die beiden Getter, aam Beispiel von GetThumbnail:
Delphi-Quellcode:
function TQueryResultClass.GetThumbnail: TMemoryStream;
  var AUser, APass : String;
begin
  if not Assigned(FThumbnail) then
  begin
    FThumbnail := TMemorystream.Create;
    Result := FillThumbnail(FThumbnail);
  end else
    Result := FThumbnail;
end;
Die Funktion Fillthumbnail:
Delphi-Quellcode:
function TQueryResultClass.FillThumbnail(var Thumbnail: TMemoryStream):TMemoryStream;
var
  BildID: Integer;
  BlobStream: TStream;
  LNull: string;
  SQLString: string;
begin
    BildID := Self.FidBild;
    SQLString := 'SELECT Bildtabelle.Thumbnail as Thumbnail FROM Bildtabelle WHERE Bildtabelle.idBild = :BildID';
    FDMySQLDml.FDQueryMain.SQL.Text := SQLString;
    FDMySQLDml.FDQueryMain.Params.CreateParam(ftInteger, 'BildID', ptInput);
    FDMySQLDml.FDQueryMain.Params[0].AsInteger := BildID;
    FDMySQLDml.FDQueryMain.Open;
    FDMySQLDml.FDQueryMain.First;
    while not FDMySQLDml.FDQueryMain.Eof do
    begin
      if not FDMySQLDml.FDQueryMain.FieldByName('Thumbnail').IsNull then
      begin
        BlobStream := FDMySQLDml.FDQueryMain.CreateBlobStream(FDMySQLDml.FDQueryMain.FieldByName('Thumbnail'), bmread);
        BlobStream.Position := 0;
        FThumbnail.CopyFrom(BlobStream, Blobstream.Size);
        BlobStream.Free;
        FDMySQLDml.FDQueryMain.Next;
        Result:= FThumbnail;
      end
      else
      begin
        LNull := 'Kein Thumbnail vorhanden';
        FThumbnail.WriteBuffer(LNull, SizeOf(LNull));
      end;
    end;
    FDMySQLDml.FDQueryMain.Close;
end;
Das ist das, was geschieht, wenn zur Darstellung der Datensätze auch auf die Bildpropertys von TQueryResultClass zugegriffen wird.

Und nochmal zur Übereinstimmung von Klassen-/Feld und Tabellen/Feldnamen: Der Klasse tuts nicht weh, wenn ihre Felder gleich heissen, wie diejenigen der Tabelle, und zu Fehlfunktionen kann es auch nicht kommen. Aber was liest sich besser:
Delphi-Quellcode:
FQueryResult.BildDescribeTabelle.bildkatID := FDQueryMain.FieldByName('bildkatID').AsInteger;
// oder
FQueryResult.Id := FDQueryMain.FieldByName('bildkatID').AsInteger;
Von den drei beteiligten Tabellen hat jede eine Id als Primärschlüssel und eine als Fremdschlüssel,und alle sind sie wichtig. Bei übereinstimmenden Namen kannst du in der Zuweisung nicht irren, bei grundsätzlich verschiedenen Namen hingegen schon.

Zitat von DeddyH:
Zitat:

Je mehr ich lese, desto mehr frage ich mich: wozu das Ganze? Soll das ein ORM werden/sein? Dann haben IMO die Tabellen- bzw. Feldnamen in den Objekten nichts zu suchen, die Objekte sollte es nicht interessieren, woher die Daten kommen oder wohin sie gehen, dafür ist eine Schicht zuständig. So wie ich es sehe bringt der ganze Aufwand momentan 0% Nutzen (sofern ich das richtig überblicke, ich kann mich auch irren), sondern sorgt eher für Verwirrung.
Gewissermassen ja. Das ganze, insbesondere der Teil mit dem Nachladen der Bilddaten, ist unter anderem mit deiner tatkräftigen Mithilfe (auf Delphi Treff) entstanden. Dein Kommentar damals: "Ein Mini-Orm".
Ein ORM bildet ja eine komplette Datenbank mit Hilfe von zB. Delphi-Objekten ab. Aber eben eine komplette Datenbank. Meine Klasse gestattet das Nachladen der Bilddaten bei Bedarf und das Iterieren durch die Ergebnismenge bei geschlossenem Query oder sogar geschlossener Verbindung, mehr nicht.


Gruss
Delbor

stahli 19. Okt 2017 16:53

AW: OOP wirklich nicht möglich?
 
Zitat:

Zitat von Delbor (Beitrag 1383694)
Gäbe es eine OOP-Lösung, um TQueryResultClass zu ändern? Änderungsziel: Gleiches Funktionieren, wie ursprünglich implementiert, aber mit andern Bezeichnern.

Ok, also ist Deine Kernfrage, wie Du die Properties der Klasse und die Klasse selbst am einfachsten umbenennen kannst?
Da wäre zu sagen: Rechtsklick / Refactoring / Umbenennen.
Das kannst Du für die Properties sowie Getter und Setter machen.

Was Du in diesem Zusammenhang mit OOP meinst, kann ich nicht nachvollziehen.

Insgesamt würde ich aber weiterhin zu einem anderen Ansatz raten.

Delbor 19. Okt 2017 19:02

AW: OOP wirklich nicht möglich?
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hi stahli

Zitat:

Da wäre zu sagen: Rechtsklick / Refactoring / Umbenennen.
In etwa so ist QueryCMResultClass entstanden, wobei ich in den meisten Fällen den Sync-Arbeitsmodus verwendet habe.
Die Frage zielte eigentlich hauptsächlich darauf ab, wie ich das nächste mal vorgehen muss, um die Sache möglichst einfach durchzuziehen.
Vor kurzem habe ich ein Interface-Testprogrämmchen begonnen.
Wenn ich die Sache richtig verstanden habe, müsste ich TQueryResultClass statt von TPersistent von TInterfacedobject, IMeinInterface abbleiten, letzteres inklusive der Member deklarieren und die Member in TQueryResultClass implementieren. Somit hätte ich, wenn ich das richtig verstehe, durchaus eine OOP-Lösung.

Gruss
Delbor

stahli 19. Okt 2017 19:55

AW: OOP wirklich nicht möglich?
 
Irgendwie verstehen wir uns nicht so richtig...

Also mit OOP arbeitest Du ja schon, da Du mit Klassen und Objekten arbeitest.

Mit Interfaces zu arbeiten ist schon auch sinnvoll, bedingt aber einige Einarbeitungszeit und erhöht den Schreibaufwand, da man die Klassenmember immer zwei mal schreiben muss und Delphi dabei wenig unterstützt.
Mit der automatischen Referenzzählung hat man ggf. einen Vorteil, weil man sich nicht um die Lebenszeit der Objekte kümmern muss. Das braucht aber auch einige Zeit, bis man damit richtig umgehen kann und das verinnerlicht.

Was auf jeden Fall eine Hilfe ist, ist die Abstraktion, die man durch Interfaces erhält.

Aber genau gegen diese Abstraktion wehrst Du Dich schon bei Deinen Klassen, weil Du die völlig abhängig von Deinen Datenbanktabellen gestalten willst. Das ist wirklich nicht sinnvoll. Wenn Du jetzt mit Deinen Datenbankabhängigen Klassen noch Interfaces unterstützen willst, dann hast Du noch mehr Aufwand und noch weniger Nutzen.

Was ich oben schon versucht habe zu vermitteln ist, dass die Businessklassen ihren Kram so erledigen sollen, dass sie optimal und logisch arbeiten, ohne irgendeinen Bezug zur Datenbank zu haben.
Dann baust Du eine Datenbank, die Ihre Aufgabe, Daten zu verwalten, gut erledigt. Da eine relationale Datenbank keine Delphi-Klasse ist, wird es in der Datenschicht irgendwie anders aussehen, als in der Business-Schicht. Wurscht!!!!!!
Jetzt brauchst Du einen Vermittler, der Daten aus den Objekten in die Datenbank schreibt oder in der anderen Richtung in die Objekte lädt.

Wenn Du das versuchen würdest, hättest Du eine gute Projektstruktur mit einer (relativ) guten Entkopplung der einzelnen Schichten.


Wenn das getan ist - und wirklich erst dann! - könnte man die Entkopplung noch weiter treiben und Interfaces einführen.
Das wäre aber der I-Punkt auf eine saubere Trennung der einzelnen Schichten.


Also mein Tipp fürs nächste Mal:

1) Businessklassen für die Buisnesslogik (ohne Abhängigkeit und zwanghafte Namensgleichheit zur Datenbank).
2) Datenbank in sich sinnvoll aufbauen (ohne Abhängigkeit auf die Klassen)
3) Datenbankmanager zum Speichern und Laden von Daten.
4) GUI nur mit Bezug auf die Business-Schicht

TigerLilly 19. Okt 2017 21:14

AW: OOP wirklich nicht möglich?
 
Nochmal. Decorator.

Mach eine Klasse, deren Properties so heißen, wie du möchtest, deren Getter+Setter aber auf deine Originale Klasse zugreifen.
Alle Methode dieser Klasse benennst du, wie du möchtest, die rufen aber nur Methoden deiner alten Klasse auf.

Wenn das nicht das ist, was du möchtest, weiß ich leider nicht weiter.

Vergiss das mit den Interfaces vorerst, das ist ein Implementierungsdetail + ich glaube, du weißt noch nicht, was du implementieren möchtest.

Delbor 19. Okt 2017 21:43

AW: OOP wirklich nicht möglich?
 
Hi TigerLilly
Zitat:

Nochmal. Decorator.

Mach eine Klasse, deren Properties so heißen, wie du möchtest, deren Getter+Setter aber auf deine Originale Klasse zugreifen.
Alle Methode dieser Klasse benennst du, wie du möchtest, die rufen aber nur Methoden deiner alten Klasse auf.
Ups! Offenbar hab ich da was überlesen, bzw. einen fatalen Denkfehler gemacht, indem ich dachte, die Basisklasse würde da (auf der von mir verlinkten Seite zum Decorator Pattern) gewissermassen mit einem Interface 'dekoriert'.

Ich werde mir da doch noch einiges a Tutorials und Theorie einverleiben müssen.

Gruss
Delbor

stahli 19. Okt 2017 22:03

AW: OOP wirklich nicht möglich?
 
Ich denke nicht, dass das Decorator-Pattern hier wirklich hilfreich ist.

Das Ziel ist ja, dass die Klassenmember so heißen, wie die Datenbankfelder.
Man müsste also die DB-Zugriffe in den Originalklassen auf die neue Datenbank anpassen und um die alten Klassen neue Klassen hüllen, die nach außen neue Namen für die alten Member anbieten.

Das wäre ja noch mehr Durcheinander als so schon.

Wenn Du von den namensgleichen Klassenmembern und Tabellenfeldern nicht abrücken willst und die Tabellenfelder neue Namen erhalten, dann ist der einfachste Weg, tatsächlich das Refactoring-Umbenennen.


Dass diese Namensgleichheit aber unnötig und nachteilig ist, habe ich ja schon öfter erwähnt. ;-)

TigerLilly 20. Okt 2017 06:57

AW: OOP wirklich nicht möglich?
 
Zitat:

Das Ziel ist ja, dass die Klassenmember so heißen, wie die Datenbankfelder.
Ja eben. 4 unterschiedliche Datenbanken --> 4 unterschiediche Datenbankfelder --> 4 Dekorators. Voila.

TiGü 20. Okt 2017 08:39

AW: OOP wirklich nicht möglich?
 
Ich verstehe nicht, warum du trotz meines Beitrages vor einigen Tagen immer noch nicht die Gemeinsamkeiten der jeweiligen Klassen in eine Basisklasse ziehst.

Stevie 20. Okt 2017 10:47

AW: OOP wirklich nicht möglich?
 
Zitat:

Zitat von TigerLilly (Beitrag 1383730)
Nochmal. Decorator.

Mach eine Klasse, deren Properties so heißen, wie du möchtest, deren Getter+Setter aber auf deine Originale Klasse zugreifen.
Alle Methode dieser Klasse benennst du, wie du möchtest, die rufen aber nur Methoden deiner alten Klasse auf.

Das nennt sich Adapter und nicht Decorator.

TigerLilly 20. Okt 2017 10:52

AW: OOP wirklich nicht möglich?
 
:oops: Da hast du recht.

Delbor 20. Okt 2017 11:11

AW: OOP wirklich nicht möglich?
 
Hi zusammen

@TiGü
Zitat:

Ich verstehe nicht, warum du trotz meines Beitrages vor einigen Tagen immer noch nicht die Gemeinsamkeiten der jeweiligen Klassen in eine Basisklasse ziehst.
Stimmt eigentlich... So, wie ich das jetzt rückwirkend sehe, war ich zu sehr beschäftigt, nach anderen Lösungsmöglichkeiten zu suchen.
Um mich selbst aus Beitrag 11 zu zitieren:
Zitat:

Apropos Gordischer Knoten: Ein Hintergedanke war auch, eine Vorfahrklasse zu erstellen, die letztlich an solche "TQueryresult-Klassen" beliebiger Struktur vererben kann, so dass darin Daten aus Tabellen beliebigen Aufbaus gespeichert werden könnten, wie zB. aus einer Adressdatenbank oder sonstwas.
Eine solche "TQueryresult-Klasse" aber kann gar keine Felder analog der Abgefragten Datenbank haben, und zwar schlicht und einfach, weil diese Felder zur Entwurfszeit einer solchen Klasse nicht bekannt sind.
An diesen Umstand hatte ich nicht mehr gedacht, als DeddyH und Stahli meinten, die Klassenmember dürften nicht so heissen wie die Tabellen-/ Feldnamen der DB.
Um TigerrLilly zu zitieren:
Zitat:

Ja eben. 4 unterschiedliche Datenbanken --> 4 unterschiediche Datenbankfelder --> 4 Dekorators. Voila.
Zum Decorator Pattern hab ich da noch eine Umsetzung mit Delphi gefunden. Die von mir zuerst verlinkte Seite enthält C++ - Codeschnipsel. Und da ich nie mit C++ gearbitet habe, kann das zu Verständnisproblemen führen.

Gruss
Delbor


Alle Zeitangaben in WEZ +1. Es ist jetzt 19:42 Uhr.
Seite 1 von 2  1 2      

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