Zitat von
inriz:
Hat mir sehr geholfen.
Das freut mich!
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.
Du meinst die Methode der Observer? Nein! Ist gar nicht nötig. Im Beispiel ist gezeigt wie man es machen könnte, du führst einfach eine Klasse TAbstractObserver ein (oder halt ein Interface). Hier ist die Methode (die du sonst als Methodenzeiger hättest) einfach abstrakt. Da diese abstrakt ist, kannst du von TAbstractObserver keine direkten Instanzen erzeugen.
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?
Hm, das kommt wohl darauf an, was du unter einer Interfaceklasse verstehst.
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:
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;
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
)
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?
Es sollte an sich keine Probleme geben. Alles was der Wizzard macht ist auch immer "per Hand" möglich. Am einfachsten ist hier immer ausprobieren. Treten Probleme auf, so schaust du einfach welche es sind und fragst ggf. in der
DP, da wird dir schon jmd. helfen
Zitat von
inriz:
Ist Mehrfachvererbung gerne gesehen oder sollte man das bei der Komponentenentwicklung verhindern?
[/quote]
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:
type
TMyClass = class(TBitmap, TList)
schon beim Compiler für Ärger sorgen. Der kann damit nichts anfangen. Dies wäre klassische Mehrfachvererbung.
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:
type
TMyClass = class(TInterfacesObject, IBitmap, IList)
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.
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!