![]() |
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:
Nun arbeite ich daran, die AnwendungsDB auf SQLite umzustellen. Leider (?) habe ich Kritik von
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; ![]() 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:
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.
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; Hat jemand einen Vorschlag? Gruss Delbor |
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. |
AW: OOP wirklich nicht möglich?
Hi Neutral General
Zitat:
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 |
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. |
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 ![]() 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 |
AW: OOP wirklich nicht möglich?
Hallo,
Zitat:
|
AW: OOP wirklich nicht möglich?
Hi hoika
Zitat:
Delphi-Quellcode:
type
TQueryResultClass = Class(TPersistent) private FidBild: Integer; FThumbnail : TMemorystream; FBitmap: TMemorystream; FBildDescribeTabelle : TBildDescribeTabelle; FKategoryTabelle : TKategoryTabelle; ...
Gruss Delbor |
AW: OOP wirklich nicht möglich?
Zitat:
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; |
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. |
AW: OOP wirklich nicht möglich?
Ich kann bei solchen Architektur Problemen diese Buch empfehlen:
![]() |
AW: OOP wirklich nicht möglich?
Hi zusammen
@Elrond Zitat:
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 |
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. |
AW: OOP wirklich nicht möglich?
Zitat:
|
AW: OOP wirklich nicht möglich?
|
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? |
AW: OOP wirklich nicht möglich?
Liste der Anhänge anzeigen (Anzahl: 1)
Hi Tigerlilly
Zitat:
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:
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 ![]() 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 |
AW: OOP wirklich nicht möglich?
Zitat:
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. |
AW: OOP wirklich nicht möglich?
Das unterstützen die gängigen OR-Mapper eigentlich auch.
|
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 ![]() Die OOP-Vorgehensweise nach dem Decorator Pattern wäre demnach also gewesen:
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 |
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. |
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.
|
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:
aufgebaut sind. Haben sie noch eine Verbindung zur Datenbank oder nicht?
FTblBildText : TTblBildText; // FBildDescribeTabelle : TBildDescribeTabelle;
FTblAlbum : TTbl_Album; // FKategoryTabelle : TKategoryTabelle; 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. |
AW: OOP wirklich nicht möglich?
Liste der Anhänge anzeigen (Anzahl: 1)
Hi zusammen
@ stahli Zitat:
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:
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:
Zitat:
Delphi-Quellcode:
Das dürfte viel schwerer zu lesen sein als:
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;
Delphi-Quellcode:
Wobei ich jetzt nur mal einiges ersetzen lassen habe - zu Demozwecken in einer neuangelegten, aber nicht gespeicherten Unit.
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; Zitat:
Zitat:
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:
(*) abgesehen davon, dass es eigentlich unsinnig ist, als Anwendungsdatenbank einen Server wie MySQL zu verwenden (Ausnahme:EmbeddedServer) Gruss Delbor |
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. |
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:
Jetzt kannst Du Objekte erzeugen, im Speicher verwalten, in Listen zuordnen und Kilometer für Fahrer und Autos zählen.
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; 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:
Dann weiß nur der DBManager, wie die Verbindung zu den Datenbanktabellen herzustellen ist.
TDBManager = class
function GetAllPersons_WithoutCars: TList<TPerson>; function LoadPerson(aPersonId: Integer): TPerson; procedure SavePerson(aPerson: TPerson); end; 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:
bzw.
FTblBild_IdBild: integer;
Delphi-Quellcode:
zu arbeiten, wäre dort
FBildTabelleIdBild: integer;
Delphi-Quellcode:
sinnvoll, weil die bei der Businesslogik eben interessiert.
fBildID: integer;
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. |
AW: OOP wirklich nicht möglich?
Liste der Anhänge anzeigen (Anzahl: 1)
Hi zusammen
@MichaelT Zitat:
Zitat:
Zitat:
Gleichwohl - die MySQL-DB bleibt vorerst bestehen. Zitat:
@stahli: Zitat:
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 |
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.
|
AW: OOP wirklich nicht möglich?
Zitat:
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. |
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:
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.
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; Das besondere, weswegen es TQueryResultClass überhaupt gibt, sind seine Bilder-Propertis:
Delphi-Quellcode:
Die beiden Getter, aam Beispiel von GetThumbnail:
property Thumbnail: TMemoryStream read GetThumbnail write SetThumbnail;
property Bitmap: TMemoryStream read GetBitmap write SetBitmap;
Delphi-Quellcode:
Die Funktion Fillthumbnail:
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;
Delphi-Quellcode:
Das ist das, was geschieht, wenn zur Darstellung der Datensätze auch auf die Bildpropertys von TQueryResultClass zugegriffen wird.
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; 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:
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.
FQueryResult.BildDescribeTabelle.bildkatID := FDQueryMain.FieldByName('bildkatID').AsInteger;
// oder FQueryResult.Id := FDQueryMain.FieldByName('bildkatID').AsInteger; Zitat von DeddyH: Zitat:
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 |
AW: OOP wirklich nicht möglich?
Zitat:
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. |
AW: OOP wirklich nicht möglich?
Liste der Anhänge anzeigen (Anzahl: 1)
Hi stahli
Zitat:
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 |
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 |
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. |
AW: OOP wirklich nicht möglich?
Hi TigerLilly
Zitat:
Ich werde mir da doch noch einiges a Tutorials und Theorie einverleiben müssen. Gruss Delbor |
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. ;-) |
AW: OOP wirklich nicht möglich?
Zitat:
|
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.
|
AW: OOP wirklich nicht möglich?
Zitat:
|
AW: OOP wirklich nicht möglich?
:oops: Da hast du recht.
|
AW: OOP wirklich nicht möglich?
Hi zusammen
@TiGü Zitat:
Um mich selbst aus Beitrag 11 zu zitieren: Zitat:
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:
![]() Gruss Delbor |
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:48 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz