![]() |
Objekterstellung im Konstruktor abbrechen
Hallo zusammen,
ich hoffe, dass diese Frage ausnahmsweise mal keine so harte Nuss ist wie meine anderen... :mrgreen: Also zum Thema: Ich habe ein Formular mit überschriebenem Create()-Konstruktor, welches dynamisch erstellt wird. Falls eine bestimmte Funktion, die in diesem Konstruktor aufgerufen wird, fehlschlägt (keine Exception, sondern Rückgabewert False), soll die Erstellung des Formulars abgebrochen und es wieder freigegeben werden. Ein Referenz-Parameter im Konstruktor gibt Aufschluss darüber, ob diese bestimmte Funktion fehl schlug oder nicht.
Delphi-Quellcode:
Klar, ich könnte statt Free (was einen bösen Fehler verursacht) eine Exception auslösen und diese beim Create-Aufruf abfangen. Aber ich möchte es einfach eleganter (-> ASuccess)... :???:
constructor TMyForm.Create(AOwner: TComponent; AMyP: Integer; var ASuccess: Boolean);
begin [...] if not MyFunc(AMyP) then begin Free(); // Oje, das endet böse... Exit; end; [...] end; Gibt es da einen anderen Lösungsweg? Ich bin dankbar für jeden Lösungsvorschlag! :-D Grüße, Marco P.S.: Damit keiner meckert: :warn: Dieser Beitrag hätte zwar auch ins Forum "VCL / WinForms / Controls" gepasst, da die Frage sich aber eher allgemein auf Objekte als speziell auf "das Formular" bezieht, habe ich sie hier reingestellt. |
Re: Objekterstellung im Konstruktor abbrechen
Wieso sind Exceptions nicht elegant?
Delphi-Quellcode:
Try
anObject := TMyObject.Create; anOnject.DoSomeThing; anObject.AProperty := FooBar Except Showmessage('Das Objekt konnte nicht erzeugt werden') End; |
Re: Objekterstellung im Konstruktor abbrechen
Überschreib TObject.ClassInstance. In der Code-Lib gibt's ein Beispiel (
![]() @alzaimar: Wenn das Haptprogramm diese Exception dann nicht abfängt... |
Re: Objekterstellung im Konstruktor abbrechen
Zitat:
Die Frage die ich mir stelle: Ist es sinnvoll das mit der Variable zu machen? Immerhin ist es ja fast normal das man mit Try-Except arbeitet. Oder noch besser gefragt: Was ist der Grund das ich ein Objekt nicht erzeugen kann? Dies sollte ja ein böser Fehler sein und dann finde ich es besser dies mit den Standardmöglichkeiten zu machen. |
Re: Objekterstellung im Konstruktor abbrechen
oder du machst eine
Class Function CheckAndCreate; Die dir die Instanz zurückliefert oder auch NIL falls deine Prüfung entsprechendes sagt.
Delphi-Quellcode:
mfg
Class Function TMyObj.CheckAndCreate : TMyObj;
begin result = TMyObj.Create; if not result.Check then begin freeandnil (result); end; end; Der Dan |
Re: Objekterstellung im Konstruktor abbrechen
Hi PHistev,
Dein Einwand ist sinnlos. Wer die Exception nicht abfängt wird auch den Flag 'Success' nicht abfragen. Aber wenn die Exception nicht abgefangen wird, passiert zumindest eins nicht: "Nil Pointer exception" Deine Variante:
Delphi-Quellcode:
Meine Variante:
...
MyObject := TMyObject.Create (Success); MyObject.DoSomething; // <-- Phatoomp, wenn Success = False, ...
Delphi-Quellcode:
Frage: Was ist besser? Was ist sicherer? Was führt vielleicht mal zu unangenehmen Effekten?
...
MyObject := TMyObject.Create; MyObject.DoSomething; // <-- Wird erst gar nicht ausgeführt, und der Anwender sieht, wie gut der Entwickler ist! ... @DerDan: Noch eine Möglichkeit. Viele Wege führen nach Rom. Aber Dein Weg ist wenigstens gepflastert :zwinker: Hat aber einen Nachteil: Es wird ein illegales Objekt erzeugt. Es kann ja sein, das das Objekt einfach nicht erzeugt werden _kann_ (oder nur mit Schwierigkeiten). Ich bleib dabei: Mit Exception ist's am einfachsten. Vielleicht sogar mit der ClassFunction von DerDan. |
Re: Objekterstellung im Konstruktor abbrechen
Zitat:
Das kann man ja in die Function Check reinlegen. Von da her seh ich keine Situation bei der man nicht mal erst ein Object anlegen können sollte. Wird übrigens bei allen anderen Varianten hier in den obigen beispielen auch gemacht |
Re: Objekterstellung im Konstruktor abbrechen
Hallo zusammen,
danke erstmal für die zahlreichen Antworten... :-D Nun, zum besseren Verständnis der Situation: Ich will direkt im Create-Konstruktor des Formulars Daten in selbiges laden. Da dies aber scheitern kann, soll bei einem Fehler die Erstellung der Form abgebrochen werden. Die Funktion zum Laden der Daten verursacht bei Fehlern (normalerweise) keine Exception, sondern gibt den Erfolg über Result zurück. Die beste Lösung meines Problems ist wohl die Kombination von "Exception" und ASuccess.
Delphi-Quellcode:
Tjaja, diese Abort-Prozedur hat mir gefehlt... :stupid:
constructor TMyForm.Create(AOwner: TComponent; AMyP: Integer; var ASuccess: Boolean);
begin ASuccess := False; [...] if not MyFunc(AMyP) then Abort(); // Aha... [...] ASuccess := True; end; [...] try TMyForm.Create(Self, 123, Success); except if not ASuccess then // Funktion gescheitert end; [...] Richtig Sinn macht ASuccess zugegebenermaßen eigentlich erst, wenn es über eine Enumeration Aufschluss über den Erfolg bzw. Nichterfolg der Formularerstellung gibt, was sich aber problemlos erweitern lässt. Viele Grüße, Marco |
Re: Objekterstellung im Konstruktor abbrechen
Ich würde mich mal näher mit Exceptions, bzw. der Programmflusskontrolle beschäftigen. Dann kannst Du ziemlich elegant deine 'Enumeration' vergessen und den Grund des Scheiterns komplett über Exceptions abbilden. Du definierst Dir einfach verschiedene Exceptionklassen, die Du individuell abfangen kannst (steht alles in der OH)
Delphi-Quellcode:
So hast Du eine strikte Trennung im Programm. Der normale Programmfluss ist klar sichtbar und die Fehlerbehandlung ist abgegrenzt.
Type
EWrongParameter = Class (Exception); EInvalidUsage = Class (Exception); ... Constructor TMyObject.Create (aParameter : TSomeType); Begin Inherited; If UsageInvalid Then Raise EInvalidUsage.Create ('Some Text'); If Not CheckParameter (aParameter) Then Raise EWrongParameter.CreateFmt('Wrong parameter type %s',[aParameter]); ... End; ... Begin Try oMyObject := TMyObject.Create; oMyObject.CanCallMethods; Except On E:EInvalidUsage Do HandleError; On E:EWrongParameter Do HandleAnotherError; // Alle anderen Fehler (Speicher etc.) werden nicht abgefangen und werden weitergeleitet! End; ... End; |
Re: Objekterstellung im Konstruktor abbrechen
Hallo Alzaimar,
Zitat:
Gruß, Marco |
Re: Objekterstellung im Konstruktor abbrechen
Hallo Marco,
irgendie dreht sich mir der Magen um wenn ich im Creator einen Destroy einleiten soll. Das ist logisch schwierig! Man könnte natürlich einen Timer erstellen, der bei fehlenden Daten im Timerevent ein Destroy auslöst, aber den für mich korrekten Weg würde ich darin sehen ein Datentestobject die Datenverfügbarkeit testen zu lassen und nach Ergebnis dann das Formular aufzubauen. Das würde auch einiges an Speicherschiebereien sparen, denn Formulare sind doch große Objecte. Viele Grüße // Martin |
Re: Objekterstellung im Konstruktor abbrechen
Hi Martin,
ich bin zwar nicht gemeint, aber ich schalte mich mal zwischen: Ich kann Deinen Einwand zwar nachvollziehen, hier aber nicht sehen. Meine Interpretation eines Konstruktors ist hier, wie der Name schon sagt, eine Aufforderung zum Erzeugen eines Dingens. So, wie z.B. ein Haus bauen. Ich sage also: "Ich gebe Dir den Auftrag, ein Haus zu bauen, Danke schon mal". Es ist doch legitim, wenn das, aus welchen Gründen auch immer, nicht hinhaut. Das war die Intention von Marco. Natürlich ist es Quatsch, die grundlegende Prüfung der Vorbedingungen in den Konstruktor zu stopfen. Genauso fatal wäre es, um beim Vergleich zu bleiben, den Auftrag fürs Häusle bauen ohne vorherige Prüfung zu geben. Jetzt weiss ich auch, worauf Du hinaus willst: Der Konstruktor sollte also nicht das Haus bauen, sondern zunächst, sagen wir, die Absicht manifestieren. Danach erfolgt die Prüfung der Bonität, der Kosten, des Bauträgers etc. Wenn hier was schiefgeht, dann wird der Vorgang eben abgebrochen.... So gesehen, sollte der Konstruktor noch gar keine konkreten Aufgaben übernehmen oder Werte festsetzen, sondern nur die Voraussetzungen schaffen. Doch, damit kann ich mich anfreunden. Nicht, das ich mir widerspreche, ich plädiere unbedingt für eine Flusskontrolle mit Exceptions (statt ständig irgendwelche Returncodes auszuwerten). Aber das Wesen eines Konstruktors wäre demnach nur die Bereitstellung eines Gerüstes ('Framework'), mit dem man die Aufgabe angehen kann. Der Aufruf des Konstruktors kann schiefgehen, das wäre aber ein GAU, wie Speicher voll, Flasche leer oder so was. Interessanter Aspekt. |
Re: Objekterstellung im Konstruktor abbrechen
N´abend Alzaimar,
ja Deinen Vergleich finde ich gut. Wollte mich in die Diskussion zu den Exception da nicht einmischen. Habe halt noch nie was im Constructor abgebrochen und da erwarte ich es in meinem Code eigentlich auch nicht. Grüße // Martin |
Re: Objekterstellung im Konstruktor abbrechen
Wenn eine Exception im Konstruktor ausgeloest wird, dann wird automatisch der Destruktor aufgerufen.
Man muss also nur den Code im Konstruktor und Destruktor auf einander abstimmen, da die normale Implementierung des Destruktors die vollstaendige Initialisierung im Konstruktor annimmt. Wenn man nun die Erstellung des Objekts in try except einschliesst, dann bekommt man das gewuenschte Ergebnis. |
Re: Objekterstellung im Konstruktor abbrechen
Hallo zusammen,
da muss ich doch auch noch ein Wörtchen zu sagen... Zitat:
Zitat:
Zitat:
Was wäre dein "Lösungsweg" nur für eine Ressourcen- und Energieverschendung, wenn man bedenkt, dass vielleicht bei 100 Versuchen ein einziges Mal ein Fehler auftritt? Alle Prüfungen umsonst... Da dreht es mir den Magen um! :pale: Zitat:
Zitat:
Warum hätte es ein OnCreate()-Handler denn nicht auch getan? Weil bei einem auftretenden Fehler der Aufbau des Formulars nicht einfach wieder abgebrochen werden könnte (oder hat da jemand eine Idee?). Und es würde auch keinen Sinn machen, das nackte Formular ohne Daten vor sich zu haben... Zitat:
Zitat:
@Robert: Jaja, das ist mir schon klar. Dies schrieb ich ja bereits im "Root Posting". :stupid: Liebe Grüße, Marco P.S.: Ich wollte hier niemanden auf irgendeine Weise angreifen oder runtermachen. Außer den Smilies kann man in ein Posting ja keinerlei Emotionen mit reinpacken, welche in einem Gespräch aber alles andere als unwichtig sind. :nerd: Das nur nebenbei bemerkt... :zwinker: |
Re: Objekterstellung im Konstruktor abbrechen
Moin, moin,
In Alzaimar Beispiel findet sich im Creator das inherited am Procedureanfang. Dadurch durchläuft das Object Formular schon einiges an Allocations und Aufbauarbeit und nur die letzte Instanz ist noch nicht geklärt, das bestätigt den Speicherhinweis, da beisst die Maus kein Faden ab. Sind Deine Prüfungen für mehrer Formulare eher gleichlaufend, dann würde ich meine Vorobjectmethode nicht wegwerfen. Zumahl Du wahrscheinlich eine zentrale Aufrufroutine haben wirst, wo die Formulare dynamisch aufgebaut werden, je nach Anwahl des Nutzers. Sind die Prüfungen sehr Formularspezifisch sehe ich auch, dass die Constructor-Variante, (ja für mich was Neues, her damit) der übersichtlichere Weg ist, da alle Aufgaben in der Formularunit liegen, ok, klingt logisch, macht Sinn! -> Mach es so ! Uhps schon wieder Teatime, ja man hat so seine Verpflichtungen... -:) Viele Grüße // Martin // PS: Texteditoren mit vielen dynamischen Formularen sind ein Fall für den Papierkorb... // |
Re: Objekterstellung im Konstruktor abbrechen
Hallo Martin,
wenn du deine Prüfungs-Methode überzeugend verteidigen willst, darfst du meinen entsprechenden Nachfragen nicht einfach ausweichen... :wink: Zitat:
Gruß, Marco |
Re: Objekterstellung im Konstruktor abbrechen
Hi Marco,
sorry, der Computer darf heute leider nicht dauernd laufen... Ja ok, ein Texteditor ist für mich keine Textverarbeitung oder IDE. Sowas mag ich klein handlich und ohne viel Schnickschnack um eben mal eine Text zu edieren: halt Notepad und gut... Wer sich schon über der die dynamische Verwaltung seiner Formulare Gedanken macht, programmiert daher mit hoher Wahrscheinlichkeit an etwas komplexeren, das liegt jedenfalls nahe. Das Thema dynamische Formulare ist für mich interessant, da ich schon des längerem an einem MDI-System arbeite, wo Teile des MDI-Formulars noch sichtbar sind und die Clients je nach Funktion eingeblendet werden und die Tastatursteuerung natürlich über die Formulargrenzen erhalten bleiben muß. Also man kann mit sowas viel Zeit verbringen. An einer Sache bin ich bisher leider auch immer noch gescheitett: Eignetlich wollte ich eine Komponente bauen, wo ich alle in frage kommenden Formulare als Liste lade. Die Komponente sollte dann nur Namen oder Nummer des Formulars bekommen, andere dann schließen und das entsprechende öffnen. Aber leider ist das daran gescheitert, das ich ja verschieden Objectypen erstellen muß und die habe ich bis daher nich in die Listenverwaltung bekommen. Na da geht wohl noch Wasser die Leine herunter.... Grüße // Martin |
Re: Objekterstellung im Konstruktor abbrechen
Ich kann mir ganz gut vorstellen, das beide Verfahren zu stabilen und übersichtlchem Code führen.
Der eine prüft eben explizit, der Andere geht das Risko ein, das die Karre gegen die Wand fährt, und räumt hinterher auf. Ich kann mich für beide Verfahren erwärmen, die Hauptsache ist doch, das man weiss, was man tut und das das Ergebnis stabil, übersichtlich und wartbar wird. Wenn ich eine vollständige Fallunterscheidung hinbekomme, unter welchen Umständen etwas schiefgehen könnte, dann implementiere ich das. Wenn nicht, dann wird mit Try....Except gearbeitet. Nur Rückgabewerte à la 'aSuccess', die zeigen, ob etwas schiefgegangen ist, verkneife ich mir normalerweise, weil sie eben aus einer Folge von Anweisungen If..Then Anweisungen machen. @mschaefer: Dein Problem hatte ich mal mit einem Pagecontrol (HideTabs=True) und einer Basisklasse (TModule, abgeleitet von TForm) gelöst, die in der Lage ist, sich auf einem TabSheet zu plazieren (Parent umbiegen). Das Hin-und-Herschalten geschieht über PageControl.ActivePageIndex. Auf einem TModule-Formular kann ich Actionlists definieren, die mit der Actionlist des MDI-Masters beim Modul-Umschalten verschmolzen werden: Ich habe so eine Grundfunktionalität, die über alle Module hin identisch sind, sowie für jedes Modul individuelle Aktionen, die nur dann sichtbar werden, wann das entsprechende Modul aktiviert ist. |
Re: Objekterstellung im Konstruktor abbrechen
... und schließlich noch mein Senf :mrgreen:
IMHO ist in so einem Falle die Prüfung bei der Erstellung immer sinnvoller als vorher einen vielleicht aufwändigen Test durchzuführen. Wer garantiert mir denn, dass es trotz vorheriger Prüfung klappt? Niemand! Wenn du z.B. mit FileExists vorher prüfst, ob eine Datei existiert, dann kann es im Konstruktor dennoch sein, dass du sie nicht öffnen kannst (keine Rechte, Sperrung, etc.). Auch kann z.B. ein Datensatz, den du vor dem Aufruf von Create überprüft hast, im Konstruktor schon wieder gelöscht sein. Was habt ihr denn alle gegen Exceptions??? Was ist sooo schlimm daran, dass eine Box mit "Es eine Ausnahmebedingung vom Typ BlaBlaBla aufgetreten" kommt anstatt eurer eigenen Box mit "ich konnte die Datei nicht öffnen"? Auf Exceptions muss man immer vorbereitet sein, denn unvorhersehbare Ausnahmebedingungen können immer auftreten. Und wenn man dieses Konzept sauber durchzieht, dann braucht man keine Vorab-Checks mehr! |
Re: Objekterstellung im Konstruktor abbrechen
Moin, moon
ja die Exceptions stehen derzeit wohl als geeignet fest. Da hat es von keinem nennenswerte Kritik gegben. Beim Thema Vorabprüfung (durchaus mit Exception) oder Prüfung im Creator zeichnet sich ja ein Muster ab. Einen Aspekt mag ich nochmal einwerfen. Für den Anwender ist es natürlich günstig, wenn er sieht ob eine Funktion verfügbar ist. Also wenn ein Menüpunkt zu einem Formular aktiv ist oder inaktiv. Für diesen wäre es natürlich günstig, wenn bei Aufruf des Menues geprüft wird. Das schließt natürlich eine Überprüfung im Creator für aufwendigere Prüfungen nicht aus. Grüße // Martin |
Re: Objekterstellung im Konstruktor abbrechen
Zitat:
Eine Exception auszulösen kostet schließlich auch etwas crunch time. ;) Aber generell ist es logisch, dass eine Klasse, die im Konstruktor einen Dateinamen will, diese auch öffnen will. Weiter ist es logisch, dass es knallt wenn die Klasse mit der Datei nicht das machen kann, was sie will. Das sollte für die meisten auch nix neues sein... Schließlich dürfte hier jeder schonmal eine Instanz eines FileStream erzeugt haben. :zwinker: Ich verstehe nur nicht ganz (eigentlich üerhaupt nicht) warum man das in eine Form-Ableitung stopfen will. Das ist so flexibel wie ein Hummer auf der Go Cart-Bahn oder ein Navi, dass fest mit dem Rahmen verschweißt ist, so dass man sich ein neues Auto bauen muss um die jährliche Update DVD verwenden zu können... |
Re: Objekterstellung im Konstruktor abbrechen
Zitat:
Zitat:
|
Re: Objekterstellung im Konstruktor abbrechen
Moin,
Vom Prinzip her wäre es natürlich schon richtiger die Funktionalität von der Anzeige zu trennen und da sehe ich Roberts Kritik das in den Formularcreator zu legen. Das Problem ist aber durch die VCL eigentlich nicht wirklich lösbar. Grüße // Martin |
Re: Objekterstellung im Konstruktor abbrechen
Zitat:
Delphi-Quellcode:
Der Konstruktor macht noch gar nichts. Er weiss doch nicht, was ich mit dem Parameter anstellen will.
Type
TFileTool = Class (TSomething) ... Public Constructor Create (aFileName : String); Procedure OpenForRead; Procedure OpenForWrite; Procedure CreateFile; Function FileSize : Integer; .... End; Ich würde hier überhaupt keine Regeln festsetzen, ob man im Konstruktor alles abchecken soll, oder nicht. Es ist eine Frage der Herangehensweise. Was IST das Objekt? Wozu ist es da? Danach richtet sich dann, was der Konstruktor machen sollte. In meinem fiktiven Beispiel instantiiert der bloss das Objekt und schreibt von mir aus den Parameter in ein privates Feld. Das wars dann aber. Man könnte hier z.B. nur prüfen, ob der Name gültig ist, oder nicht. Aber wenn ich den Namen später austauschen kann, dann bringt das auch nicht viel, weil die Gültigkeit des Parameters beim Konstruktor doch noch gar keine Rolle spielt. Beim Konstruktor eines Formulars wird ja ne ganze Menge angestellt (Handles holen etc.). Da würde ich mir schon überlegen, ob da nicht mal eine Überprüfung nach dem Motto "Can I Create it?" nicht doch sinnvoll wäre. Aber, wie Flocke schon erwähnte, und was durch die Murphyschen Grundregeln ('If it can go wrong, it WILL go wrong') manifestiert ist, Exceptions lauern überall. Ich kann natürlich Exceptions auch anders interpretieren, nämlich als 'Ausnahme'. Sind sinnlose Parameter dann Ausnahmen? Oder sind Ausnahmen wirkliche Programm(ier)fehler, die die Kacke zum Dampfen bringen? Das sei doch jedem selbst überlassen. Ich finde, man sollte sich darüber Gedanken machen, und die Linie dann mehr oder weniger durchziehen. Denn ein Programm wird umso übersichtlicher und stabiler, je orthogonaler ich meine (Programmier-) Philosophie und Interpretation dort einfliessen lasse. Dann weiss ich, das eine Exception ein GAU ist, IMMER (oder eben nicht, je nach Philosophie). Es ist immer eine Frage des Geschmacks, wie ich meine Flusskontrolle realisiere. So hat z.B. die Rückgabe von Statuswerten auch seine Berechtigung, nämlich dann, wenn ich mich nicht immer drum scheren muss, ob es nun geklappt hat, oder nicht. Bei einem Konstruktor ist das natürlich selten sinnvoll, weil ich i.A. noch etwas mit dem Objekt anstellen will. |
Re: Objekterstellung im Konstruktor abbrechen
Hallo zusammen,
danke für die interessante Diskussion. :) Ich würde sagen, es ist abhängig von der Situation, wie man das Ganze nun realisiert. In meinem Falle (den ich in den vorherigen Postings ja wirklich schon zu genüge beschrieben habe ;) ) ist der beste Weg die Exceptions, wie gesagt. Sicher, man könnte (sicherheitshalber) noch triviale Vorprüfungen wie z.B. FileExists() vorneanhängen. Aber wie gesagt, ein solcher Festplattenzugriff ist wahrscheinlich noch langsamer als ein gescheiterter Formularaufbau... Aber wir drehen uns glaube ich im Kreis. :D Zitat:
Gruß, Marco |
Re: Objekterstellung im Konstruktor abbrechen
Zitat:
Oder, anders ausgedrückt: Wenn ich weiss, was und wie ich programmiere (also meinen Stil durchziehe), dann werden Programme automatisch stabil(er) und wartbar(er). Oder, noch anders ausgedrückt: Erst planen, dann coden. |
Re: Objekterstellung im Konstruktor abbrechen
Hallo alzaimar,
ok, jetzt hab ich's verstanden... :shock: :wink: Gruß, Marco |
Re: Objekterstellung im Konstruktor abbrechen
Zitat:
Ob ich dir in der Sache zustimme, bin ich mir aber nicht ganz sicher. An "erst denken, dann coden" besteht zwar nicht viel Zweifel, aber IMHO nur insofern sich das auf die Sachfragen bezieht. Solange die sauber gelöst werden, ist der Stil, denke ich, mehr oder weniger egal. |
Re: Objekterstellung im Konstruktor abbrechen
Hallo zusammen,
okay, da wir nun mehr oder weniger vom bereits gelösten Thema abkommen, ist es denke ich nun an der Zeit, diesen Thread offiziell als erledigt zu kennzeichnen. (Solch einen Button sollte nicht nur bei "offenen Fragen" geben :warn:). Ich danke euch für die interessante Diskussion! :thumb: Bis auf bald, Marco P.S.: Ach ja, vielleicht könntet ihr mal schnell im VCl/Controls-Forum vorbeischauen? :lol: Da ist leider noch so eine ![]() |
Alle Zeitangaben in WEZ +1. Es ist jetzt 15:27 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