![]() |
Delphi-Version: 5
Interface richtig umgesetzt?
Hallo, auf Grund eines anderen Threads, wurde mir vorgeschlagen, ich solle zur Umsetzung eines Vorhabens ein Interface entwickeln. Ich möchte mit MessageQueues arbeiten, mir aber offen halten, welchen Broker ich benutze bzw. in Zukunft benutzen will. Nun habe ich ein Interface entwickelt und soweit alles umgesetzt. Das Wechseln zwischen Brokern geht auch. Nun wollte ich dennoch wissen, ob ich alles richtig umgesetzt habe. Vielleicht mache ich mir dennoch zu viel Arbeit da man es auch einfacher Schreiben kann.
Meine Vorgehensweise: 1. Interface deklarieren 2. TInterfacedObject von Interface ableiten (für MSMQ) und Methoden erstellen 3. TInterfacedObject von Interface ableiten (für ActiveMQ) und Methoden erstellen 4. Eigenes Objekt erstellen, welches die gleichen Methoden hat, wie das Interface. So kann ich dieses Objekt programmweit benutzen. Intern ruft es die Methode des jeweils benötigten InterfaceObjects auf. Vielleicht blöd erklärt, daher hier nochmal ganz knapp der Quelltext: 1. Eigenständige Unit für Interfaces & Co.
Delphi-Quellcode:
Programmiert wird dann nur noch mit Hilfe von TMy_MQ in folgender Form
//Das Interface
IMy_MQ = interface ['{5C3F02B1-2E73-4500-9CD3-02EAFEDB332C}'] function CreateQueue(path: String): Boolean; end; TMQBroker = (mqMS, mqActiveMQ); //Objekt für Microsoft MessageQueue TMy_MSMQ = class(TInterfacedObject, IMy_MQ) function CreateQueue(path: String): Boolean; end; //Objekt für Apache ActiveMQ TMy_ActiveMQ = class(TInterfacedObject, IMy_MQ) function CreateQueue(path: String): Boolean; end; //Objekt, welches programmweit benutzt wird TMy_MQ = class(TObject) private fBroker: TMQBroker; fMQ: IMy_MQ; public constructor Create(mqBroker: TMQBroker = mqMS); destructor Destroy; function CreateQueue(Path: String): Boolean; property Broker: TMQBroker read fBroker write fBroker; end; ////////// function TMy_MSMQ.CreateQueue(path: string): Boolean; begin ShowMessage('MSMQ '+path); end; function TMy_ActiveMQ.CreateQueue(path: string): Boolean; begin ShowMessage('ActiveMQ '+path); end; constructor TMy_MQ.Create(mqBroker: TMQBroker = mqMS); begin fBroker:=mqBroker; if (mqBroker = mqMS) then begin fMQ:=TMy_MSMQ.Create; end else if (mqBroker = mqActiveMQ) then begin fMQ:=TMy_ActiveMQ.Create; end; end;
Delphi-Quellcode:
Soll der Broker gewechselt werden, einfach im Construkter den gewünschten eintragen.
procedure TForm1.Button2Click(Sender: TObject);
var sacom_mq: TSacom_MQ; begin Sacom_MQ:=TSacom_MQ.Create(mqActiveMQ); //oder halt eben mqMS Sacom_MQ.CreateQueue('test'); Sacom_MQ.Destroy; end; Macht man das so, oder kann ich mir den Weg über TMy_MQ irgendwie ersparen? Danke sehr! |
AW: Interface richtig umgesetzt?
Also wenn das Dein Code ist, dann stimmt etwas vorne und hinten nicht (oder ich verstehe einfach nicht :D).
Warum implementiert eine MessageQueue Klasse das MessageQueue-Interface, die andere aber nicht? Der Enum im Create der MessageQueue ist auch unschön - hier müsstest mit einem neuen Broker jedes mal den Konstruktiv anfassen. Um Dir korrekt helfen zu können wäre es schön, alles erstmal korrekt zu benennen (den Broker erkennt man nämlich nicht am Namen ;-)). Und zu den Begrifflichkeiten: Eine Klasse leitet oder erbt von anderen Klassen. Interfaces werden von einer Klasse implementiert (da ja nur die Schnittstelle vorgegeben wird) - sie leitet also nicht vom Interfaces ab. |
AW: Interface richtig umgesetzt?
Mein Vorschlag (und meine Präferenz) wäre es, für jeden Broker eine konkrete Implementierungsklasse zu bauen die ein allgemeines Interfaces unterstützt.
Minimales Beispiel:
Delphi-Quellcode:
Irgendwann muss man sich dann für die konkrete Klasse entscheiden:
IMessageBrokerClient = interface
... end; TActiveMQClient = class(TInterfacedObject, IMessageBrokerClient) ... end; TMSMQClient = class(TInterfacedObject, IMessageBrokerClient) ... end;
Delphi-Quellcode:
Die Clientklassen können natürlich auch von einer abstrakten Basisklasse abgeleitet sind in der allgemeine Dinge und Properties untergebracht sind, die für alle Broker genutzt oder unterstützt werden:
var
MyClient: IMessageBrokerClient; begin MyClient := TActiveMQClient.Create; ... end;
Delphi-Quellcode:
Nachtrag: die verschiedenen Lösungen beim Einsatz von Interfaces unterscheiden sich oft auch darin, welche Delphi Units konkret einkompiliert werden. Wenn man eine Klasse pro Broker hat, wird nur diese dem Projekt hinzugefügt. Wenn man eine Klasse hat über deren Konstruktorargument man den Broker auswählen kann, dann muss in der Unit dieser Klasse Code für jeden unterstützten Broker enthalten sein. Dadurch würde man - ausser durch Verwendung von einigen IFDEFs - mehr in die Anwendung packen als man braucht.
TAbstractClient = class(TInterfacedObject, IMessageBrokerClient)
... end; TActiveMQClient = class(TAbstractClient, IMessageBrokerClient) ... end; |
AW: Interface richtig umgesetzt?
Danke.
@mjustin: Mache ich mir da nicht die Flexibilität kaputt, die ich durch das Interface erreichen wollte? In meinem Beispiel müsste ich bei einem Brokerwechsel nur eine Zeile für die gesamte Anwendung ändern. Bei deiner Lösung müsste ich ein komplette Objekte tauschen. Warum nutzt du in diesem Fall ein Interface? Vielleicht übersehe ich auch was wegen meinem Schnupfkopf ;) |
AW: Interface richtig umgesetzt?
Zitat:
zu machen ist unschön und nimmt dir die Flexibilität, da Du diese Klasse nun doch an die Implementierung (TActiveMQ.Create) koppelst, obwohl Du ein Interfaces dafür hast (IMy_MQ). Der korrekte Ansatz wäre hier, wenn überhaupt, eine fertige MessageQueue mit in den Konstruktiv zu übergeben (IMY_MQ) und somit diese Klasse von der Implementierung von IMy_MQ zu entkoppeln. Zitat:
Um das Beispiel von mjustin zu vollenden, hättest Du z.B. noch einen TMessageQueueProvider (oder wie auch immer man das nennen möchte:
Delphi-Quellcode:
IMessageQueueProvider = interface
.... function CreateMessageQueue(const AMessageBrokerType: TMessageBrokerType): IMessageBrokerClient; end; TMessageQueueProvider = class(TInterfacedObject, IMessageQueueProvider) public function CreateMessageQueue(const AMessageBrokerType: TMessageBrokerType): IMessageBrokerClient; end; .... implementation function TMessageQueueProvider .CreateMessageQueue(const AMessageBrokerType: TMessageBrokerType): IMessageBrokerClient; begin case AMessageBrokerType: mqMS: Result := TMy_MSMQ.Create; msAtiveMQ: Result := TActiveMQ.Create; else raise EMessageQueueBrokerNotSupportedError.Create(''); end; .... |
AW: Interface richtig umgesetzt?
Zitat:
(Man könnte aber durch Einsatz von IFDEF verschiedene Versionen der Anwendung erstellen, die je nach Bedarf nur einen Message Broker oder eine Auswahl (zwei bis N) unterstützen.) |
AW: Interface richtig umgesetzt?
Wie willst Du mit diesem Interface eigentlich einen TCP-Client umsetzen?
Du musst anders herum anfangen: Beschreibe die Funktionalität und die Methoden, die Du für die Kommunikation benötigst, ohne die Begriffe TCP, UDP, MSMQ, Schnur, RS-232 und Buschtrommel zu verwenden. Also: Mein Client soll sich immer nur mit einem konkreten Server unterhalten. Dafür brauche ich eine 'Connect' (muss die wirklich Connect heißen) Routine. Oder besser (und allgemeiner) : StartConnection. Dann will ich dem eine Nachricht als String schicken können (SendMessage). Dann will ich noch eine Anfrage schicken und auf das Ergebnis (auch ein String) warten. Aber nur maximal X Sekunden. (QueryResponse) Und zu guter Letzt möchte ich noch, das der Client wieder offen für einen anderen Server ist (StopConnection) Fertig ist das allgemeine Client-Interface
Delphi-Quellcode:
Fertig ist das Interface. Für die Nachrichten könntest Du nun auch noch ein allgemeines Interface bauen, aber so geht es erst einmal. Natürlich fehlt noch z.B. die Stream-Funktion, aber das kannst Du ja später machen.
Type
IClientConnection = Interface procedure StartConnection (server : IServer); procedure SendMessage (message : String); procedure QueryResponse (query : String; var response : String; timeout : Integer); procedure StopConnection; end; Schreibe nun eine konkrete TMSMQClientConnection-Klasse, die das IClientConnection-Interface implementiert. Danach schreibst Du noch ein TTCPClientConnection-Klasse, die auch dieses Interface implementiert. Dann noch eine RS-232-Klasse Und eine Consolen-Klasse: SendMEssage => WriteLn und QueryResponse = 'WriteLn' und 'ReadLn'... Sehr schön zum testen. Und eine Buschtrommel-Klasse mit Mikrofon, Lautsprecher und Samples. Und eine LTE-Klasse Und. Und. Und. Deine Anwendungen, die dieses Interface verwenden, werden auch mit einer LTE-4G-Astromedial-Klasse funktionieren. Und, besser noch: Sie müssen noch nicht einmal neu kompiliert werden (wenn man die Klasse per Plugin aus einer DLL lädt, z.B.) Zitat:
|
AW: Interface richtig umgesetzt?
Zitat:
Delphi-Quellcode:
Im Interface fehlt noch die Angabe des Queuenamens (oder allgemeiner: Destinationnamens, da es neben Queues auch Topics oder Channel gibt, die Begriffe sind nicht einheitlich über alle Broker). Man könnte den Destinationnamen zum Beispiel als Argument beim Erzeugen der Connection übergeben.
type
IConnection = interface procedure Start; procedure Send(AMessage: string); function Receive(ATimeout: Integer): string; procedure Stop; end; IMessageBrokerClient = interface function CreateConnection: IConnection; ... end; |
AW: Interface richtig umgesetzt?
Hi,
Dejan Vu, das habe ich soweit verstanden. So habe ich es auch umgesetzt (nur, dass bei mir erstmal alles auf Broker zugeschnitten ist, da ich TCP verworfen habe. Aber okay, lassen wir es mal bei deiner Skizze). Ich dachte, dass das Interface mir ermöglicht, dass ich meinen Quelltext nicht ändern muss, wenn ich den Broker tausche (in meinem Fall). Also, dass ich im Programm selbst nur gegen TAllgemeineKlasse programmiere, welche dann im Hintergrund die geforderten Broker benutzt. In deinem Beispiel muss ich ja dann jeden Aufruf von TTCPClientConnection in TMSMQClientConnection ändern. Und ich dachte, dass mir das Interfaces ersparen. Daher habe ich meine Klasse TMy_MQ erstellt, gegen die ich programmiere. Es kanna aber auch wirklich sein, dass ich das mit den Interfaces noch missverstehe. |
AW: Interface richtig umgesetzt?
Zitat:
Falls im Programm an verschiedenen Stellen eine neue Instanz der Klasse benötigt wird, kann man die Erzeugung in eine zentrale Factorymethode auslagern. Auch dann wird nur ein Mal die konkrete Klasse eingebunden. |
AW: Interface richtig umgesetzt?
Zitat:
Danke |
AW: Interface richtig umgesetzt?
Zitat:
Diese eine Instanz wird dann in einer Factory Methode verwendet die eine Connection liefert:
Delphi-Quellcode:
Und dann kann im Programm an beliebigen Stellen - die natürlich auf die MyApp Instanz zugreifen können - eine Connection erzeugt und geöffnet werden, zum Beispiel in einem Thread.
function TMyApp.CreateConnection: IConnection;
begin Result := MyMSMQBrokerClient.CreateConnection; end;
Delphi-Quellcode:
(Exceptionbehandlung für Verbindungsverlust etc. mal weggelassen).
procedure TMyDoWorkThread.Execute;
var Conn: IConnection; Msg: string; begin Conn := MyApp.CreateConnection; Conn.Start; while not Terminated do begin Msg := Conn.Receive(1000); // mach etwas mit der erhaltenen Nachricht... end; Conn.Stop; end; |
AW: Interface richtig umgesetzt?
Ok, verstanden.
Hab gerade etwas rumprobiert und gesehen, dass in deinem Beispiel MyMSMQBrokerClient.CreateConnection einen Zeiger auf IConnection zurückgibt (denke ich). Doch ich bekomme den Inhalt für "CreateConnection" nicht hin. Was muss ich konrekt reinschreiben, um eine IConnection zurückzubekommen? Danke für eure Geduld ;) EDIT Ich glaube, ich habe es! Interfaces - Die Klasse TMyMQ wird gelöscht, da überflüssig (wie vermutet)
Delphi-Quellcode:
und hier kommt in der programmweiten Verwendung der Knackpunkt. Es wird nicht TMy_MQ erstellt, sondern direkt eine Variable IMy_MQ, welche dann via TMy_MSMQ.Create mit dem Interfacezeiger gefüllt wird:
IMy_MQ = interface
... ... end; TMy_MSMQ = class(TInterfacedObject, IMy_MQ) ... ... end;
Delphi-Quellcode:
Richtig? Es funktioniert jedenfalls. Aber hatte meine erste Version ja auch ;)
procedure TForm1.Button2Click(Sender: TObject);
var mq: IMy_MQ; begin mq:=TMy_MSMQ.Create; mq.SendMessage; end; |
AW: Interface richtig umgesetzt?
Sieht sauberer aus als am Anfang, es ist aber auch viel weniger Code :P
|
AW: Interface richtig umgesetzt?
Das freut mich. Nun muss es auch nur richtig sein ;)
Es ergibt sich aber jetzt dadurch das Problem, dass ich dennoch viel eingeschränkter hinsichtlich Parameter bin. Angenommen, mein Interface hat die Methode "Open". Mit dieser möchte ich entweder bei MS die Queue öffnen, oder eben bei ActiveMQ. Nun fordert MSMQ bei dessen Open-Methode Parameter A, B, C, ActiveMQ fordert Parameter 1, 2. Da ich nun durch
Delphi-Quellcode:
nur Zugriff auf die Interface-Methoden habe, bleiben die Methoden/Felder von TMy_MSMQ verborgen. Nun stehe ich also vor dem Problem, dass ich nicht weiß, wie ich innerhalb meiner Open-Methode auf die verschiedenen Erfordernisse der jeweiligen Message-Queue Objekte eingehen kann.
var
mq: IMy_MQ; begin mq:=TMy_MSMQ.Create; EDIT: Ich könnte natürlich auch einen Cast nach TMy_MSMQ durchführen, um auf die Methoden/Felder dieser Klasse zuzugreifen. So könnte ich Parameter setzen, die dann wieder in "Open" ausgelesen werden. |
AW: Interface richtig umgesetzt?
Gut, wenn sich die Parameter so unterscheiden, dann hast Du zusätzlich zu der "allgemeinen" Schnittstelle auch noch speziellere Schnittstellen, mit entsprechenden Methoden.
Dann kannst Du das aber nicht sauber mit einem allgemeinen Typ abbilden (auch mit Klassen nicht). Du musst quasi schon VOR dem Verwenden wissen, ob es sich um eine MQ mit 2 oder mit 3 Parametern handelt. Die Optionen die ich hier sehe sind also: - Du versucht eine allgemeines "Open" zu definieren und die eine Klasse verwendet alle 3 Parameter, die anderen nur 2 (das ist aber nicht gerade die feine englische Art) - zwei separate "Basis" Interfaces, einmal für das Open mit 3 Parametern, einmal mit 2 Parametern - ein Basis-Interface mit zwei Spezialisierungen (abgeleitete Interfaces, jeweils mit der eigenen Open-Definition) Allerdings kenne ich auch hier nicht Deinen kompletten Anwendungsfall. Des Weiteren stell ich mal die Frage: gibt es nicht bereits vorhandene Implementierungen im Netz ? |
AW: Interface richtig umgesetzt?
Vielen Dank für die Antworten.
Ausgangspunkt war folgender: Ich wusste nicht, welche MessageQueue-Lösung ich final im Produkt verwenden möchte. Für den Anfang reicht MSMQ. Vielleicht kommt irgendwann mal ein MAC-Client o.ä. dazu. Dann hab ich mit MSMQ schlechte Karten und müsste auf ActiveMQ (oder einen anderen) umsteigen. Daher riet mir Sir Rufo, ein Interface zu implementieren und später beim Umstieg nur einen winzigen Teil im Programm ändern müsste. Daher ein Interface mit z.B. "Open" (welches die MessageQueue öffnet). MSMQ verlangt zwei Parameter. ActiveMQ mit Sicherheit ganz andere. Und darauf weiß ich gerade nicht zu reagieren, außer einen Typecast zum TMy_MSMQ-Objekt oder einem Array im Interface namens "Params", die vor dem Open (oder anderen Methoden) entsprechend gefüllt und in der Methode ausgelesen und wieder gelert, quasi als Container. Wenn wir die Interface-Geschichte mal außen vor lassen: Mit der MSMQ komme ich nun in den Grundzügen klar, kann erstellen, lesen, schreiben etc. |
AW: Interface richtig umgesetzt?
Also wenn ich einen Brief versenden will, dann muss ich trotzdem nicht das Postamt öffnen, oder den Briefkasten aufschließen. Ich gehe einfach zu meinem Interface
Delphi-Quellcode:
und sende den Brief ab mit
IBriefPost
Delphi-Quellcode:
. Soll sich das Interface doch selber darum kümmern. ;)
IBriefPost.Sende( Brief);
|
AW: Interface richtig umgesetzt?
Ich verstehe, was du damit sagen willst. Bleiben wir dabei: Es gibt die Post, TransoFlex und UPS. Bei allen kann ich noch Post fragen. Dafür habe ich IMessages.GetMessage.
Nun kann ich bei der Post auswählen, ob nur das Vorliegen neuer Post erfragt werden soll und wer das machen darf (Open MQ_RECEIVE_ACCESS, MQ_DENY_NONE), erfragen und gleich mitnehmen <bei Post löschen> aber nur spezielle Leute das machen dürfen (Open MQ_PEEK_ACCESS, MQ_DENY_RECEIVE_SHARE) etc. Bei TransoFlex kann ich genau das gleiche machen, nur nicht festlegen, wer diese Aktionen durchführen darf. UPS will garnicht genau wissen, was ich vorhabe, sondern ich kann mich selbst dort bedienen. Wie soll ich darauf mit IMessage.GetMessage reagieren? Ich habe als Zwischenlösung (nur damit ich weiter testen kann) ein Array mit Parametern angelegt:
Delphi-Quellcode:
Funktioniert, ist aber nicht optimal...
procedure TMy_MSMQ.SetParam(Value: string);
begin SetLength(fParams, Succ(Length(fParams))); fParams[High(fParams)]:=Value; end; ... procedure TMy_MSMQ.QueueOpen begin fQueue:=fQueueInfo.Open(StrToInt(fParams[0]), StrToInt(fParams[1])); //MQ_SEND_ACCESS / MQ_RECEIVE_ACCESS etc. end; procedure TForm1.Button2Click(Sender: TObject); var mq: IMy_MQ; begin mq:=TMy_MSMQ.Create; //Verbinden mq.SetParam(IntToStr(MQ_RECEIVE_ACCESS)); mq.SetParam(IntToStr(MQ_DENY_NONE)); if (mq.QueueOpen('DIRECT=OS:.\Private$\Test')) then ShowMessage('Queue opened'); end; |
AW: Interface richtig umgesetzt?
Zitat:
Zitat:
Das ist insofern unschön, da Du einen undefiniertem Wert übergibst den Du innerhalb der Open-Implementierung ja wieder auswerten musst- man sieht innerhalb der Open-Implementierung also gar nicht auf den ersten Blick was gemeint ist. Alternative mit Parametern zu Deinem Beispiel: Sofern Du dabei bleiben möchtest, ein MQ-Interface bereitzustellen mit einem allgemeingültigen Open-Aufruf, dann solltest Du die Parameter sogt wie möglich typisieren. Du hast ja überall definierte Zustände, das bedeutet alle "Rechte" kannst Du als Enums darstellen (wie Du diese letztendlich gestaltest überlass ich mal Dir), die man dann entsprechend auch einfach und leserlich auswerten kann. Wenn möglich könntest Du sogar ein großes Enum deklarieren und in der Open-Methode einfach ein Enum-Set verlangen, z.B:
Delphi-Quellcode:
Jede Implementierung entscheidet dann selbst, ob und wie die übergebenen Parameter verwertet werden. Der Vorteil ist, Du musst beim austauschen der MQ-Implementierung nicht den Open-Aufruf anpassen und siehst gleich am Namen des Enums um was für ein Recht es sich handelt und kannst entsprechend einfach darauf prüfen und es auswerten. Nachteil ist weiterhin , dass man hier keine Rückmeldung erhält, sofern gesetzte (Rechte)Parameter ignoriert werden.
TMQSetupType = (mqsReceiveAccess, mqsDenyNone, mqsPeekAccess, mqsDenyReceiveShare);
TMQSetupTypes = set of TMQSetupType; IMyMQ= interface ... procedure Open(const ASetup: TMQSetupTypes); end; ..... procedure TMy_MSMQ.QueueOpen begin fQueue:=fQueueInfo.Open([mqsSendAccess, mqsReceiveAccess]); end; Alternativen: Du solltest Dir einfach mal die MQ-Implementierungen anderer Sprachen anschauen und herausfinden wie das dort gelöst wurde (der Anwendungsfall sollte ja exakt der gleiche sein) - vielleicht findet sich da ja eine elegante Lösung, oder jemand anderem im Forum fällt bis dahin was schickes ein. |
AW: Interface richtig umgesetzt?
Es ist doch unsinnig die Open Methode im Interface zu deklarieren. Wozu soll die gut sein?
Ich will eine Nachricht senden. Ob dafür bei MSMQ erst die Queue erst mit welchen Parametern auch immer geöffnet werden muss oder bei TrallalaMQ erst ein Purzelbaum geschlagen werden muss, das interssiert bei der konkreten Implementierung aber nicht das Interface. Das ermöglicht mir das Senden einer Nachricht. Das Wie regelt die Implementierung. Das Empfangen geht genauso: Was alles gemacht werden und erfolgen muss, darum kümmert sich die Implementierung. Das Interface hat z.B. 2 Methoden HasMessage und GetMessage. Wenn man das Interface zu konkret aufbaut, dann arbeitet man am Interface-Gedanken vorbei und kann sich das auch gleich sparen. |
AW: Interface richtig umgesetzt?
Hi,
erstmal vorab: Ich wünsche euch allen eine frohe Weihnachtszeit! Ich nutze gerade mal die Gelegenheit zwischen den Essen und Familienfeiern, auf die Nachricht hier zu antworten: Das Problem habe ich verstanden. Nur da ich ja als Rückgabeparameter das Interface erhalte, sehe ich auch nur die Mehtoden, die es hat, nicht aber die der TMy_MSMQ-Klasse. Würde ich jetzt aber im ganzen Programm mit der TMY_MSMQ-Klasse arbeiten, so wäre ich wieder an dieses Objekt gebunden und der gewünschte Vorteil des Interfaces wäre nicht mehr gegeben. Ebenso, wenn ich das den Interface-Zeiger nach TMy_MSMQ caste. Ich muss da noch irgendwas falsch verstehen... Ich wüsste also derzeit nicht, wo ich die Open-Methode deklarieren könnte, damit ich mit ihr arbeiten kann. Entweder darf ich mir kein Interface-Zeiger zurückgeben lassen, dann könnte ich eben mit der My_MSMQ.Open arbeiten, bin dann aber programmweit an sie gebunden. Oder ich nutze das Interface und habe eben diese Parameter-Probleme. So ist derzeit meine Sicht - ich weiß, da ist was falsch, aber ich komme nicht drauf was. Oder gibt es für mein konkretes Vorhaben nicht diese Lösung, dass ich bei einem Provider-Wechsel nur eine Quelltextzeile austauschen muss? Nochmal der Vollständigkeit halber - Dies ist mein letzter "Entwicklungs"-Stand:
Delphi-Quellcode:
Danke und weiterhin noch schöne Feiertage!
procedure TForm1.Button2Click(Sender: TObject);
var mq: IMy_MQ; //Interface begin mq:=TMy_MSMQ.Create; //Objekt vom Typ "TMy_MSMQ" //Verbinden mq.SetParam(IntToStr(MQ_RECEIVE_ACCESS)); //Notlösung mq.SetParam(IntToStr(MQ_DENY_NONE)); //Hier auch ;) if (mq.QueueOpen('DIRECT=OS:.\Private$\Test')) then ShowMessage('Queue opened'); end; |
AW: Interface richtig umgesetzt?
Der grundlegende Denk-Fehler ist, dass du der Meinung bist, in der Anwendung alles Mögliche über das Interface geben zu müssen. Aber eben genau diese Implementations-spezifischen Informationen habe dort nichts verloren.
Nehmen wir mal den ganz simplen Fall, dass wir einfach nur einen Text versenden wollen. Dann sähe das interface so aus
Delphi-Quellcode:
So, jetzt wollen wir diesen Text wie in deinem Beispiel über MSMQ versenden:
IMy_MQ = interface
procedure Send( const AText : string ); end;
Delphi-Quellcode:
In der Implementierung von
procedure TForm1.Button2Click(Sender: TObject);
var mq: IMy_MQ; //Interface begin mq := TMy_MSMQ.Create( 'DIRECT=OS:.\Private$\Test', MQ_RECEIVE_ACCESS, MQ_DENY_NONE ); //Objekt vom Typ "TMy_MSMQ" mq.Send( 'Mal was senden' ); end;
Delphi-Quellcode:
kann ich doch nun ganz gemütlich überprüfen, ob die Queue schon geöffnet wurde, wenn nicht, die Queue öffnen und einfach den Text versenden. Genauso eben auch Empfangen. Der Anwendung kann es wie gesagt völlig egal sein, was alles passieren muss, damit ein Text versendet werden kann. Das ist Aufgabe der Implementierung dieses entsprechend zu gewährleisten.
TMy_MSMQ
(Das das Beispiel jetzt nicht ganz passt, weil du hier eine Empfangs-Queue öffnen willst und ich etwas senden möchte weiß ich, es sollte aber den Ansatz hinreichend erklären). Denk immer daran, wie das im wahren Leben funktioniert. Egal ob du mit UPS, DHL, Hermes, etc. ein Paket verschicken möchtest, bei allen ist folgendes gleich:
Das ist dann ein Interface :) |
AW: Interface richtig umgesetzt?
Super Erklärung!
Aber dann habe ich wohl technisch was falsch umgesetzt. Denn ich kann nicht auf die Methoden von TMy_MSMQ zugreifen. Ich würde nur die Methode "Send" sehen, welche vom Interface kommt. Denn "mq" ist vom Typ IMy_MQ. Ich hatte es ja schon probiert, der Klasse TMy_MSMQ neben den Methoden des Interfaces noch weitere Methoden und Felder hinzuzufügen. Aber die waren mir in OI immer verborgen. Ich kann nicht darauf zugreifen. Was müsste denn richtigerweise .Create von TMy_MSMQ zurückgeben? Oder ist das der Grund, warum du die Parameter im Constructor übergibst? |
AW: Interface richtig umgesetzt?
Wieso denn jetzt auf einmal OI? Möchtest du eine Komponente bauen?
Ja, kann man auch machen, würde ich allerdings nicht, denn warum sollte ich auf eine Form sowas draufklatschen, das gehört für mich ein paar Ebenen tiefer in die Anwendung. Anyway, bauen wir uns also so eine Sende-Komponente:
Delphi-Quellcode:
// Basis Komponente für alle Send Queues
TCustomSendQueue = class( TComponent ) protected procedure DoSend( const AText : string ); virtual; abstract; public procedure Send( const AText : string ); // ruft OnSend und DoSend auf published // Ein paar Events? property OnSend : TNotifyEvent; end; // Konkrete Komponenten für MSMQ TMSMQConnection = class( TComponent ) published property Server : string; property ... end; // Konkrete Ableitung für MSMQ SendQueue TMSMQSendQueue = class( TCustomSendQueue ) protected procedure DoSend( const AText : string ); override; published property Connection : TMSMQConnection; property Queue : string; property ... end; |
AW: Interface richtig umgesetzt?
Guten Morgen.
Es tut mir schrecklich leid - ich habe mich vertran. Ich schiebe es mal auf meine Erkältung. Ja, ich schrieb OI, meinte aber lediglich die Codevervollständigung. Dort werden mir die weiteren Methoden nicht angezeigt. Eine Komponente macht da wirklich keinen Sinn. Das Interface hat z.B. nur GetMessage, SendMessage. TMy_MSMQ hat darüber hinaus noch SetEncryption, IsActiveDirectory, etc.pp (alles fiktiv). Bei meinem Codebeispiel von oben sehe ich wie gesagt nur die Methoden des Interfaces Get/SendMessage. Ich weiß also nicht - außer mit einem TypeCast - wie ich auf die Methoden der KLasse zugreifen kann. Daher denke ich, noch irgenetwas falsch zu machen. Dass ein Interface minimalistisch und allgemein gehalten werden soll, habe ich jetzt verstanden. Weiß daher jetzt auch, warum es nur Get/SendMessage zu haben braucht. Deine Erklärung mit Post etc. war echt gut. Es wird. Langsam, aber es wird ;) Danke (auch für die Geduld) |
AW: Interface richtig umgesetzt?
Aber warum willst du auf die Methoden der implementierenden Klasse zurückgreifen?
Wenn du eine Nachricht verschicken willst, dann benutze das Interface und verschicke die Nachricht. Es ist Aufgabe der implementierenden Klasse sich um den gesamten konkreten Versandablauf zu kümmern, denn nur die weiß, wie man z.B. mit MSMQ sprechen muss. Ich als Versender muss doch gar nicht wissen, was da intern alles passiert, es muss nur passieren. Am sinnvollsten ist es dem Konstruktor der implementierenden Klasse alles mitzugeben, damit ich ein funktionierendes Interface bekomme. Generell würde ich allerdings auch eine Trennung zwischen Sende- und Empfangs-Queue vornehmen (also 2 Interfaces). Die Möglichkeit besteht ja, dass ich x Sende-Queues und y Empfangs-Queues benötige. Und die Sende-Queue ist idR nicht die Empfangs-Queue (warum soll ich mir selber Nachrichten schicken). |
AW: Interface richtig umgesetzt?
Okay, habe ich jetzt so alles gemacht, wie du es gesagt hast. Leuchtet alles ein, funktioniert auch soweit.
Nur irgendwie werde ich den Verdacht nicht los, dass ich mich - zumindest bei den MessageQueues - mit den Interfaces einschränke. Ich habe nun zwei Interfaces, eines für das Senden, eines für das Empfangen. Habe jetzt auch zwei implementierende Klassen (obwohl ich die zusammenführen könnte - mach ich noch). Wenn eine Nachricht gesendet wird, prüft die implementierende Klasse, ob es die Queue schon gibt. Wenn nicht, wird sie erstellt, dann geöffnet und die Nachricht eingetragen. Soweit okay. Der Versender kümmert sich also um nichts - wie ich bei der Post. Beim Erstellen einer Queue kann man aber so viele Optionen angeben, wie übermittle ich die? zB Journal, Transaktionen (IsTransactional), Zugriff (IsWorldReadable), Verschlüsselung, ACL, etc. Und das sind nur die Optionen für das Erstellen einer Queue. Ich kann ja nicht alle Parameter, die ich irgendwann mal gebrauchen könnte, in dem Constructor definieren. Verstehst du mein Problem? Allerdings habe ich auch verstanden, dass sich die Methode "SendMessage" um alles kümmern soll. Trotzdem habe ich noch obiges Problem. Würde ich nur eine Klasse erstellen, würde ich zwar die Vorteile des Interfaces verlieren, aber da hätte ich diese Probleme nicht. Habe ich noch ein altes Klassendenken? :lol: Ach unabhängig davon: Wenn die Queue bei MSMQ nicht existiert, muss man um sie zu erstellen, einen Pfad benutzen, um sie zu öffnen/lesen/löschen einen Formatnamen. Also gibt es da auch wieder unterschiede, auf die ich nicht zu reagieren weiß. |
AW: Interface richtig umgesetzt?
Anders herum gefragt:
Du hast da eine Poststelle wo alle ihre Post hinbringen, auf dass diese versendet wird. Wer legt das konkrete Verhalten der Poststelle fest? Die Mitarbeiter, die ihre Post versendet haben wollen? Oder eventuell eher doch die Geschäftsleitung? Und nochmal: Was interessiert es dich wie die Queue funktioniert (mit Transaktion oder ohne, mit Schleifchen oder Bändern, in rot oder grün) wenn du nur etwas versenden willst und du bekommst etwas geliefert, womit du versenden kannst. Ich denke du springst zu sehr zwischen den Ebenen in deiner Anwendung hin und her. Wenn die Anwendung z.B. ein Konstrukt aus Sende- und Empfangs-Queue benötigt, dann gib ihr das so
Delphi-Quellcode:
Und schon gehören die zusammen. Das ist wie Lego. Man hat kleine Steine und steckt sich da größere, spezialisierte Dinge zusammen.
TFoo = class
constructor Create( SendQueue: ISendQueue; ReceiveQueue: IReceiveQueue ); // entweder Methoden procedure Send(...); function GetMessage(..):...; // oder als Eigenschaften property SendQueue : ISendQueue; property ReceiveQueue : IReceiveQueue; end; |
AW: Interface richtig umgesetzt?
Um nochmals die Poststelle aufzugreifen:
Die Poststelle kann ja durchaus mehrere Versandkanäle benutzen, die z.B. abhängig von Größe, Gewicht und Volumen genutzt werden. Die Mitarbeiter liefern trotzdem ihre Sachen an die Poststelle und die kümmert sich intern um die Bestimmung in welche Queu jetzt das geschoben wird. Und der Empfang von ausserhalb geht auch durch die Poststelle und kommt aus unterschiedlichen Queues. Die Mitarbeiter bekommen davon nichts mit, denn die kennen nur ... die Poststelle.
Delphi-Quellcode:
TPostStelle = class( TInterfacedObject, ISendQueue, IReceiveQueue )
private FSendQueues : // Liste mit den möglichen Queues und dem Regelwerk, wann welche genutzt wird FReceiveQueues : // Liste aller möglichen Empfangs-Queues public // Erzeugen mit allen benötigten Informationen constructor Create( SendQueueRuleSet: ...; ReceiveQueues : ... ); procedure Send(...); // ISendQueue function GetMessage(...):...; // IReceiveQueue end; |
AW: Interface richtig umgesetzt?
Zitat:
Ich denke Sir Rufo bringt es hier auf den Punkt: Zitat:
|
AW: Interface richtig umgesetzt?
Danke für die Antworten. Ich habe das alles jetzt mal umgesetzt und die Parameter in den Konstruktor gepackt. Das kann so nicht richtig sein.
Nochmal ganz konkret: Ja, ich rufe nur noch SendMessage() auf und die ganze Magie passiert in der implementierenden Klasse, weil mich das nicht interessiert was da vor sich geht. Okay. Doch wenn die Queue nicht existiertm, muss sie erst angelegt werden. Dann muss ich doch sagen können, wie sie angelegt werden soll. Parameterübergabe entweder im Konstruktor oder garnicht. Richtig? Im Interface bei Send/Receive kann ich sie auch nicht übergeben, da die Send-Parameter bei MSMQ andere sind, also bei anderen Queues oder via TCP. Also müssten sie alle im Konstruktor übergeben werden - auch die, die vllt. gerade garnicht benötigt werden. Oder muss ich mich dann auf Standard-Werte festlegen. Wenn eine Queue also nicht existiert, wird sie halt immer mit den von mir in der implementierenden Klasse festgelegten Standardwerten festgelegt. Also zB immer nicht transaktionell und nie weltlesbar. Nun aber zur Umsetzung: Dies wären die möglichen Parameter: MSMQ Create: Pathname, IsTransactional, IsWorldReadable MSMQ Open: Formatname, Access (MQ_READ_ACCESS etc..), ShareMode MSMQ Send: Subj, Body, DestinationQueue, Transaction MSMQ Receive: Transaction, WantDestinationQueue, WantBody, ReceiveTimeout, WantConnectorType
Delphi-Quellcode:
Ich habe verstanden, dass sich das implementierende Objekt um alles kümmern soll. Aber ich muss doch irgendwie Einfluss darauf haben, wie es die Queue im Notfall erstellen soll.
IMy_MQReceiver = interface
['{EECD3A9D-D9BC-4644-9D69-DADE10E65ED2}'] function ReceiveMessage(): String; end; IMy_MQSender = interface ['{5C3F02B1-2E73-4500-9CD3-02EAFEDB332C}'] function SendMessage(subject, msg: string): Boolean; end; TMQBroker = (mqMS, mqActiveMQ); TMy_MSMQ = class(TInterfacedObject, IMy_MQSender, IMy_MQReceiver) private public constructor Create(createPathName: String; createTransactional, createWorldReadable: Boolean; openAccess, openShareMode: LongInt; openFormatName: String; readTransaction: LongInt; readWantBody: Boolean; readTimeout; sendDestQueue: IMSMQQueue3; sendTransaction: LongInt); destructor Destroy(); function ReceiveMessage(): String; function SendMessage(Subject, msg: String): Boolean; end; // Und so könnte dann das Erstellen des Objektes aussehen: procedure TForm1.Button4Click(Sender: TObject); var mq: TMy_MSMQ; begin mq:=TMy_MSMQ.Create('meineNeueQueue', false, false, MQ_READ_ACCESS, MQ_DENY_SHARE, '.\Private\meineNeueQueue', 0, true, INFITE, nil, 0); end; Habe mich gerade länger damit beschäftigen können und verschiedene Methoden ausprobiert. Bin jetzt gerade etwas verwirrt ;) |
AW: Interface richtig umgesetzt?
Bis auf die Tatsache, dass du eine Schreib-Lese-Queue erzeugen möchtest ist das doch alles richtig. Ich wüsste keinen Fall, wo ich in ein und derselben Queue lesen und schreiben möchte. Queues sind One-To-One Verbindungen. Eine Nachricht in einer Queue wird von einem Empfänger empfangen. Will ich an mehrere Empfänger die gleiche Nachricht senden, dann muss die Nachricht auch in mehrere Queues.
Warum sollte es falsch sein, der implementierenden Klasse alles benötigte an die Hand zu geben um zu funktionieren? Mal abgesehen, dass es für dich ungewohnt ist anscheinend nicht alles in der Hand zu haben, wo siehst du in deiner Anwendung das konkrete Problem? Zeig doch mal etwas halbwegs konkretes, wo du beim Benutzen des Interfaces den Wunsch verspüren würdest, z.B. den Namen der Queue zu ändern, oder die Queue auf Transaktion umzustellen. BTW Das passt aber nicht
Delphi-Quellcode:
Bei SendMessage hast du Subject und Message aber bei ReceiveMessage gibt es nur einen String zurück. Was kommt denn da? Subject, Message oder beides zusammengewürfelt?
IMy_MQReceiver = interface
['{EECD3A9D-D9BC-4644-9D69-DADE10E65ED2}'] function ReceiveMessage(): String; end; IMy_MQSender = interface ['{5C3F02B1-2E73-4500-9CD3-02EAFEDB332C}'] function SendMessage(subject, msg: string): Boolean; end; |
AW: Interface richtig umgesetzt?
Zitat:
Zitat:
Zitat:
Aber danke für diese Hinweise - hätte ja wirklich ein echter Fehler sein können. Die Interfaces etc werden dann später auch nicht mehr "TMY..." lauten ;) --- Aber wenn wir schon dabei sind: Kannst du mir nochmal folgendes erklären Zitat:
Vielen Dank an alle! |
AW: Interface richtig umgesetzt?
Zum einen muss man ja auch nicht nur einen Konstruktor haben und zum anderen kann man auch eine Factory nehmen.
Für eine minimalistische Queue brauchst du z.B. nur 2 Parameter, dann erstell dir einen Konstruktor mit diesen 2 Parametern und für die superduper Gedöns-Queue benötigst du 42 Parameter, jo, dann eben noch so einen, und evtl. noch ein paar weitere. Eine Factory hilft immer dann, wenn man sehr viele Instanzen benötigt mit sehr vielen gleichen und wenigen variablen Parametern. Dann erzeuge ich die Factory mit den gleichbleibenden Parametern und hole mir die Instanzen über die Angabe der noch fehlenden, aber eben variablen Parameter. Schon bleibt das hübsch übersichtlich.
Delphi-Quellcode:
Sieht ja schon einfacher aus :)
TMSMQQueueFactory = class
constructor Create( Server, { ganz viele Parameter } ); function CreateSendQueue( const QueueName : string ) : ISendQueue; function CreateReceiveQueue( const QueueName : string ) : IReceiveQueue; end; Das Beispiel mit dem Postamt wird bei dir jetzt nicht so richtig passen, es ging mehr um die Möglichkeiten. Ein Regelwerk könnte ja darin bestehen, dass z.B. Chat-Nachrichten in eine andere Queue kommen, als andere Nachrichten. Dann muss dein Postamt z.B. den Nachrichtentypen erkennen und sucht sich die passende Queue für die Chat-Nachrichten heraus und versendet darüber. |
AW: Interface richtig umgesetzt?
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 12:32 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