|
Antwort |
Registriert seit: 18. Okt 2005 15 Beiträge |
#1
Hallo,
ich brauche Rat von euch in Bezug auf Komponentenentwickeln unter Delphi 7. Mein Konzept ist an die TDatasource Komponente angelehnt, die als zentraler "handler" zwischen konnektierten TQuery Objekten und z.B. Grid Objekten fungiert. Mein Konzept sieht folgende Komponenten vor. Zum einen eine Komponente TZForm (der Name lässt anmuten, das TZForm von TForm abgeleitet wurde, das ist jedoch nicht der Fall) und einer Komponente namens TZFormElement. TZFormElement enthält eine Propertyvariable ZForm, die den Namen eines TZForm Objektes beinhaltet bzw. zugewiesen wird. Ausserdem haben TZFormElemente neu definierte Events wie z.B. OnValidate. Ist es jetzt möglich an alle konnektierten TZFormElemente und nur die konnektierten ein Event OnValidate zu senden, um die Validationsfunktion einzuleiten? D.h. ein Aufruf durch MyZForm.Validate; veranlasst die Validation aller TZFormElemente ... Ich danke für jeden Rat |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#2
HI,
das was du hier verwenden solltest ist bekannt als das Observer-Pattern. An sich ist die Idee sehr einfach, du möchtest mehr als eine Komponente konsistent über einen Zustand informieren (das Problem samt Lösung ist im Prinzip schon von dir beschrieben). Bei dem Pattern gibt es zwei verschiedene Typen, einen Observable und beliebig viele Observer. Das Observable Objekt bietet dabei eine Möglichkeit, dass sich Observer bei ihm für ein Ereignis registrieren/deregistrieren. Für die Implementierung gibt es verschiedene Möglichkeiten, ich persönlich würde dir zu der OOP Lösung raten. Diese setzt einfach die Mittel der OOP ein, so kannst du entweder ein Interface erstellen oder eine abstrakte Basisklasse verwenden, die eine Methode enthält, die aufgerufen werden soll, sobald das Ereignis eintritt. Jeder Observer muss das Interface implementieren bzw. von der Basisklasse erben. Beim Registrieren oder Deregistrieren wird einfach die Instanz die sich registriert an das Observable übergeben, das dieses speichert (z.B. in einer Liste). Beim Auftreten des Events wird über die Liste iteriert und bei jeder gespeicherten Instanz die entsprechende Methode aufgerufen. Das Argument der Methode benachrichtigt dabei über den neuen Zustand. [Add] Ups, zu früh weggeschickt. Hier noch ein kurzes Beispiel:
Delphi-Quellcode:
Gruß Der Unwissende
TAbstractObserver = class(TObject)
public procedure onValidate(const s : String;); virtual; abstract; end; TObserverA = class(TAbstractObserver) private .... protected .... public procedure onValidate(const s : String); override; .... end; TObserverB = class(TAbstractObserver) private .... protected .... public procedure onValidate(const s : String); override; .... end; TObservable = class(TObject) private FObserver : TObjectList; protected procedure notifyValidation(const s : String); public procedure addObserver(const Observer : TAbstractObserver); // ist klar, einfach in die Liste eintragen procedure removeObserver(const Observer : TAbstractObserver); // bzw. entfernen end; ... procedure TObservable.notifyValidation(const s : String); var i : Integer; begin if self.FObserver.Count > 0 then begin for i := 0 to self.FObserver.Count - 1 do begin // benachrichtigt alle registrierten Observer über das Ereignis TAbstractObserver(self.FObserver[i]).onValidate(s); end; end; end; [/Add] |
Zitat |
Registriert seit: 18. Okt 2005 15 Beiträge |
#3
Hallo,
danke für deine Antwort. Hat mir sehr geholfen. Eine Frage steht noch im Raum und zwar soll OnValidation ein Funktionspointer sein damit es flexibell durch den Programmierer definiert werden kann. Greets ps: Wo platziere ich Interfaceklassen in einer Komponenten richtig? Mir fiel auf, das der Komponentenwizzard einen Komponentenrumpf erstellt, der schon eine Elternklasse beinhaltet. Darf ich diese Elternklasse nachträglich ändern und meine Interfaceklasse einklemmen? Oder wird meine Komponente danach nicht richtig registriert? Ist Mehrfachvererbung gerne gesehen oder sollte man das bei der Komponentenentwicklung verhindern? |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#4
Zitat von inriz:
Hat mir sehr geholfen.
Zitat von inriz:
Eine Frage steht noch im Raum und zwar soll OnValidation ein Funktionspointer sein damit
es flexibell durch den Programmierer definiert werden kann. ObserverA und ObserverB erben zwar beide von TAbstractObserver, sie können aber selbst entscheiden was sie in der OnValidation Methode machen wollen. Damit hat ein Entwickler schon die maximale Flexibilität (er muss ja nur etwas von der abstrakten Basisklasse ableiten. Im Prinzip ist das nur die Objekt Orientierte Entsprechung des Funktions-/Methodenzeigers. Ich nenne gerade die beiden getrennt, da hier auch der Vorteil der Objekt Orientierten Lösung liegt. Bei Funktionszeigern können keine Methodenzeiger übergeben werden und umgekehrt! Das heißt, man muss vorher wissen ob es sich um die Methode einer Klasse (ihrer Instanz) handelt oder nicht (of Object oder nicht). Dies entfällt hier, da man ein Objekt verwenden muss. Da man die Implementierung beliebig gestalten kann, ist die Flexibilität nicht eingeschränkt!
Zitat von inriz:
Wo platziere ich Interfaceklassen in einer Komponenten richtig?
Ich glaube ich hab da nicht deutlich genug gemacht, dass ein Interace und eine Klasse zwei verschiedene Dinge sind. Es gibt drei wichtige Typen in der OOP: Interfaces, Klassen und abstrakte Klassen. Wie eine Klasse aufgebaut ist und wie man die benutzt und so ist dir sicherlich klar. Abstrakte Klassen sind natürlich sehr nah an einer "normalen" Klasse dran. Der eigentliche Unterschied liegt darin, dass eine abstrakte Klasse nie direkt instanziert werden darf. Es gibt Sprachen die Klassen auch als abstrakt markieren können, so dass der Compiler schon die Instanzierung verbietet. Delphi gehört leider nicht dazu. Eine Klasse in Delphi kann auch nicht direkt als abstrakt markiert werden. Dies ist nur für Methoden möglich. Enthält eine Klasse eine abstrakte Methode, muss die Klasse schon als abstrakte Klasse angesehen werden. Abstrakt ist eine Methode dann, wenn nur ihre Signatur bekannt gegeben wird (Name, Rückgabetyp, Parameter), aber die Methode noch nicht implementiert wurde. Der Aufruf einer solchen Methode ist natürlich nicht möglich (es gibt keine Implementierung). In Delphi führt die zu einem EAbstractError (aber erst zur Laufzeit). Der Sinn solcher abstrakten Methoden liegt (wer hätte es gedacht) in der Abstraktion. Erbt eine Klasse von einer solchen Abstrakten, so muss hier die Methode implementiert werden. Dabei kann jeder Nachfahre eine komplett eigene Implementierung haben, die Signatur ist aber immer die gleiche. Dies wird in Delphi auch gerne verwendet. Man benutzt als Parameter gerne abstrakte Datentypen, die Implementierung ist damit beliebig austauschbar (und man muss nichts an den Methoden ändern). Ein Beispiel das erst neulich in der DP kam war das folgender Code:
Delphi-Quellcode:
Mit 30.000 Datensätzen etwas über 5 min benötigt hatte. SourceList ist dabei eine TStringList gewesen. Ich empfahl dem Threadsteller es mit einer THashedStringList zu versuchen was die Zeit auf 5 Sek. senkte (zu seiner Zufriedenheit )
for i := 0 to SourceList.Count - 1 do
begin if not Terminated then begin iFindResult := DestList.IndexOf(SourceList.Strings[i]); if iFindResult <> -1 then begin // Zu einer dritten Liste hinzufügen end; end else begin break; end; end; Jedenfalls kann man hier die Austauschbarkeit von Instanzen gut sehen. Ist SourceList ein Funktionsparameter und wird einfach als TList übergeben (eine abstrakte Basisklasse) stehen alle Funktionen zur Verfügung (Strings[i], IndexOf), egal ob man eine TStringList verwendet oder eine THashedStringList (oder andere Nachfolger). Man braucht die Methoden nicht mehr zu ändern! Das Observer-Pattern wäre dann ein weiteres Beispiel. Ok, soweit zu abstrakten Klassen. Interfaces sind anders als eine abstrakte Klasse reine Schnittstellenbeschreibungen. Anders als bei abstrakten Klassen kennt ein Interface keine Variablen und alle Methoden sind autom. abstrakt. Properties können definiert werden, sind aber nur über Methoden zugänglich! Eine abstrakte Klasse kann einen Teil der Methoden implementieren und einen anderen nicht, ein Interface kann keine Methode selbst implementieren. Eine Klasse hat immer genau einen Vorfahren. Von dem erbt sie alle Methoden und Eigenschaften. Man spricht hier gerne von Verhaltensvererbung. Erbt eine Klasse eine Methode (ohne diese zu überschreiben oder zu verdecken), so liegt das gleiche Verhalten beim Aufrufen dieser Methode wie bei ihrer Elternklasse vor (es ist der exakt gleiche Aufruf). Auch viruelle (und damit auch alle abstrakten) Methoden der Elternklasse müssen auch überschrieben werden. Möchte man jetzt aber die Methoden von mehr als einer Klasse erben, hat man ein Problem. Mehrfachvererbung wird in den meisten Sprachen (auch Delphi) nicht erlaubt. Hier ist eine Lösung durch Interfaces gegeben. Ein Interface beschreibt einfach nur Properties und Methoden. Dabei ist nur die Schnittstelle bekannt (es gibt die Methoden mit dem und dem Namen, dem Rückgabetyp und den Parametern). Jede Klasse, die ein Interface implementiert sichert also nur zu, dass diese Methoden mit entsprechender Signatur zur Verfügung gestellt werden. Was diese intern machen ist das Geheimnis der implementierenden Klasse. In Delphi gibt es allerdings einige Hürden die man in der Arbeit mit Interfaces nehmen muss. So ist Delphi (in meinen Augen) nie für die OOP entwickelt wurden und man merkt es der Sprache an vielen Stellen an. Interfaces werden eigentlich nicht direkt von der Sprache unterstützt, vielmehr verwendet man spezielle COM-Konstrukte, die die Arbeit etwas umständlicher machen. So gibt es eine GUID die man setzen sollte, man kann ein Interface nicht wirklich gut casten und es fehlt das analoge Verhalten zu normalen Klassen (an einigen Stellen). Trotz alledem sind sie ein mächtiges Werkzeug für das saubere Modellieren und die OOP auch in Delphi. Der Mehraufwand hält sich in Grenzen (es nervt halt nur manchmal). Dadurch, dass ein Interface aus der COM Welt stammt, werden drei Methoden automatisch an alle Interfaces weitergereicht, diese findest du wenn du in der OH nach IInterface schaust. Möchtest du diese Methoden nicht selbst implementieren, sollte deine Klasse von TInterfacedObject (oder einem Nachfahren) abgeleitet werden. Du findest im Tutorials Bereich der DP (wenn ich mich richtig erinner) auch ein Tutorial zum Thema Delphi und Interfaces. Das sollte dir etwas besser den Sinn und Hintergründe erklären als ich es jetzt getan habe. Es bleibt jedenfalls der Fakt, dass Interaces und Klassen nicht das Selbe sind.
Zitat von inriz:
Mir fiel auf, das der Komponentenwizzard einen Komponentenrumpf erstellt, der schon eine
Elternklasse beinhaltet. Darf ich diese Elternklasse nachträglich ändern und meine Interfaceklasse einklemmen? Oder wird meine Komponente danach nicht richtig registriert?
Zitat von inriz:
Ist Mehrfachvererbung gerne gesehen oder sollte man das bei der Komponentenentwicklung verhindern?
Hier bin ich mir nicht sicher, wie du das meinst. Mehrfachvererbung heißt eigentlich, dass man von mehr als einer Klasse (gleichzeitig) erbt. Dies ist so in Delphi nicht möglich. Man kann nur eine Klasse als Vorfahren haben (und beliebig viele Interfaces implementieren). Würdest du eine Klasse erschaffen wollen, die die Funktionaltität einer TBitmap und einer TList vereint, so würde etwas wie
Delphi-Quellcode:
schon beim Compiler für Ärger sorgen. Der kann damit nichts anfangen. Dies wäre klassische Mehrfachvererbung.
type
TMyClass = class(TBitmap, TList) Anders sieht es aus, wenn du die Interaces IBitmap und IList hättest. Hier kannst du von einer beliebigen Klasse erben (z.B. TInterfaceObject) und die Methoden der Interfaces implementieren
Delphi-Quellcode:
Es sieht zwar aus, als würdest du von 3 Klassen erben, aber nur das erste Argument darf eine Klasse sein, der Rest müssen Interfaces sein. Damit, dass du diese hier angibst, sicherst du nur zu, dass TMyClass alle Methoden der beiden Interfaces implementiert.
type
TMyClass = class(TInterfacesObject, IBitmap, IList) Wenn du mit Mehrfachvererbung meinst, dass man von mehreren Klassen erbt (jedoch hierachisch), das ist absolut ok und gerne gesehen! Das siehst du daran, wenn du dir die Vererbungshierachie in Delphi anschaust. Man versucht immer ein möglichst fertige Basisklasse zu wählen. Natürlich kann man auch alles von TObject ableiten und hätte damit eine sehr flache Hierachie, aber der Sinn der Vererbung wäre damit auch verloren gegangen. An sich empfiehlt es sich sogar immer erst eine abstrakte Basisklasse zu schaffen. Dies hat mehrere Vorteile. Einen hast du bei der TList gesehen, aber auch TStream oder die TCustomControlvarianten (also auch TCustomButton, ...) zeigen dass man gerne mit solche Klassen arbeitet. Einer der Vorteile ist, dass du jederzeit neue Implementierungen einführen kannst, z.B. ein Projekt mit ähnlichen aber nicht gleichen Anforderungen. Ist hier schon eine Basis gegeben, führen Verbesserungen an einer Basisklasse zu Verbesserungen an allen anderen Klassen. Dadurch das ein Teil schon gegeben ist, kann dieser wiederverwendet werden. Dies spart in viellerlei hinsicht Zeit und Geld, da die Entwicklung entfällt, aber auch Testfälle erhalten bleiben! Dann gibt es noch das Problem, dass man ein Projekt nicht in jeder Größe allein bearbeiten kann (kein wirkliches Problem!). Man zerlegt also das Gesamtproblem in seine Teilprobleme und zum Schluss muss alles zusammen funktionieren. Da nicht alle Probleme gleich schwer sind, kann man nicht davon ausgehen, dass sie alle zur gleichen Zeit fertig sind. Hier kann man aber durch Abstrakte Klassen und Interaces die Schnittstellen vorab festlegen. Die Abhängigkeiten werden somit aufgelöst. Wird in einem leichten Problem die Lösung eines schweren benötigt, so kennt man zu diesem die Schnittstellen. Die eigentliche Implementierung kann jederzeit leicht getauscht werden ohne dass die Methoden verändert werden müssen. Um hier also die Abhängigkeit zu umgehen, kann man einfach eine Dummy-Impelementierung verwenden und mit dieser arbeiten. Ist irgendwann die konkrete "richtige" Lösung fertig, so kann diese leicht die Dummy-Impelementierung ersetzen und weder Testfälle noch Methoden müssen verändert werden. Dies wird letztlich nur durch das Vererben ermöglicht. Je verzweigter hier die Hierachien sind, desto mehr Punkte lassen sich nur finden um anzusetzen. An sich spricht also nichts dagegen sehr viele Klassen zu schaffen, von den dann wieder die nächste erbt und von der wieder eine usw. Trotzdem sollte man natürlich schon beachten, dass man auch sinnvoll vererbt. Es wäre etwas fragwürdig, wenn man einen abstrakten Taschenrechner schafft, der einen Nachfahren T1 hat, der die Addtion hinzufügt. T1 wird dann von T2 beerbt der dann subtraktion hinzufügt, von dem erbt T3 und bringt die Multiplikation mit, ... Das wäre auch nicht falsch, aber so wirklich sinnvoll halt auch nicht! |
Zitat |
Registriert seit: 18. Okt 2005 15 Beiträge |
#5
Hallo,
ich danke dir für den aufschlussreichen Beitrag. Die signifikanten Unterschieden zwischen abstrackten Klassen und Interfaces war mir so noch nicht bekannt. Ich weiss jetzt aber welches Konzept der OOP ich wo benutzen werde. Zudem muss ich sagen das mir das allgemeine OOP Konzept (viele Dinge unterscheiden sich in Bezug auf c++) bzw. die Realisierung der Implementierungen in Delphi sehr gut gefällt, vorallem der Aspekt schnell ans Ziel zu kommen. In der zwischen Zeit hab ich ein wenig rum probiert und einige Ideen implementieren können. So auch meine eigene absrakte ZDataSource Klasse, die es später erlauben soll, die vermeintliche Datenquelle durch die Implementierungen der konkreten Klassen zu bestimmen (z.B. DBMS, XML-Datei, INI-Datei ...). Ich werde erstmal die DP Tutorials durchstöbern, um auf dieser Grundlage Fragen zu stellen. Best regards |
Zitat |
Registriert seit: 13. Dez 2003 Ort: Berlin 1.756 Beiträge |
#6
Zitat von inriz:
Zudem muss ich sagen das mir das allgemeine OOP Konzept (viele Dinge unterscheiden sich in Bezug auf c++) bzw. die Realisierung der Implementierungen in Delphi sehr gut gefällt, vorallem der Aspekt schnell ans Ziel zu kommen.
Zitat von Alan Kay:
"I invented the term Object-Oriented, and I can tell you I did not have C++ in mind."
Kind regards Der Unwissende |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |