Nettes Thema
Ich hatte mal etwas dazu zusammen gestellt:
http://www.delphipraxis.net/183702-i...-factorys.html
Also ich würde mal 3 Punkte aufzählen:
+ Entkopplung
+ Austauschbarkeit
+- Referenzzählung
Entkopplung:
Wenn Du ein TAuto konstruierst, musst Du ein TMotor, TLenkrad und TRad benutzen und diese irgendwo verbinden.
Dazu musst Du bei der Verwendung von Klassen die kompletten Units kennen und einbinden.
Wenn der Motor jetzt z.B. wissen müsste, in was für einem Auto er steckt und ob das Auto gerade auf einem Prüfstand fährt
, dann muss TMotor u.U. TAuto kennen.
So hat man schnell überkreuzende Referenzen und muss sehen, dass man diese Probleme irgendwie löst.
Auch hat man indirekt ziemlich viel Ballast (in Form von Klassenmembers), den man später gar nicht braucht.
Wenn Du mit Interfaces arbeitest, kannst Du das leichter abstrahieren.
Kurz gesagt, man arbeitet nur noch mit dem Wesentlichen.
Es interessiert dann nicht mehr, wo und wie die Klasse deklariert ist. Ich habe etwas mit festgelegten Eigenschaften und Methoden und kann das benutzen ohne mich mit der ganzen Klasse befassen oder diese kennen zu müssen.
Austauschbarkeit:
Es gibt ein Auto, einen Motor, Lenkrad und Räder. Was das genau für Räder sind, Luftbereift, Vollgummi oder was auch immer spielt keine Rolle.
Wenn so ein
Rad eine bestimmte Aufnahme auch wieder als Schnittstelle beinhaltet kannst Du später die Räder durch andere Klassen ersetzen (z.B. AntiGravitationsräder) wenn diese die gleiche Schnittstelle für die Räderbefestigung haben.
Auf "klassischem" Wege müsstest Du die neuen Räder von TRad ableiten, um dieses in der Autoklasse verwenden zu können. Wenn aber das neue
Rad auch von TNeuesMaterial abgeleitet werden müsste, damit eine Werkstatt das herstellen kann, hat man ein Problem.
Mit Verwendung von Interfaces kann das neue
Rad mehrere Erfordernisse unterstützen
TNewWheel = class(TInterfacedObject, IWheel, INewMaterial)
Jetzt kann die Werkstatt mit dem Objekt umgehen, da es NewMaterial ist und das Auto kann damit fahren, weil es ein
Rad ist.
Man kann auch leicht DummyObjekte bauen, die erst einmal bestimmte Funktionen simulieren.
Wenn Du das mit echten Klassen machen willst, musst Du direkt in Dein Projekt eingreifen und das später wieder korrigieren.
Wenn Du mit Interfaces arbeitest, kannst Du leichter von außen ein DummyObjekt in Dein Projekt herein reichen, das so tut, als wäre es ein echtes Businessobjekt, aber intern einfach erst mal bestimmte Funktionen simuliert (und auch von einer völlig anderen Basisklasse abgeleitet ist).
Referenzzählung:
Das ist Fluch und Segen, aber auch optional.
InterfacedObjects in Delphi zählen mit, wie oft sie benutzt werden. Fällt die letzte Nutzung weg (indem einer Variablen z.B. Nil zugewiesen wird oder das Programm den Scope verlässt, in dem die Variable erzeugt wurde, dann wird das Objekt freigegeben.
Man muss bzw. darf daher für Interface-Variablen nie Free aufrufen. Statt dessen darf man Nil zuweisen, was aber beim Verlassen eines Scopes auch automatisch erfolgt.
Also man muss Interface-Variablen nicht freigeben, wenn man es will, dann nur durch Nil-Zuweisung.
Eigentlich ist das eine ganz nette Sache, aber es hat zwei Haken:
Wen ich in aMotor ein Motor-Interface habe und ich Nil zuweise (aMotor := Nil) kann es sein, dass das Objekt noch nicht freigegeben wird, weil irgendwo im Speicher möglicherweise noch ein Zugriff auf dieses Interface existiert (z.B. in einer Liste oder so).
Durch meine Nil-Zuweisung wird der RefCounter verringert (z.B. von 2 auf 1) und das Objekt aber erst bei 0 freigegeben.
Für die Liste existiert der Motor noch.
Wenn man mit Objekten arbeitet und hier aMotor.Free angegeben hätte, könnte es später bei Zugriff auf die Motoren-Objekte in der Liste knallen.
Bei Motoren-Interfaces in der Liste knallt nix, da das Objekt noch nicht freigegeben wurde.
Wenn ich aber vorhin das Objekt wirklich FREIGEBEN wollte, dann kann das Projekt den Motor noch verwenden, der eigentlich aber nicht mehr existieren sollte.
Ok, welche fehlerhafte Arbeit des Projektes ist die schlechtere? Das kann man so nicht entscheiden. Wichtig ist, dass man bei Vewrwendung von Interfaces mit Referenzzählung beachten muss, dass man nicht ohne weiteres in der Hand hat, wann das Objekt tatsächlich freigegeben wird.
Noch komplizierter wird es bei gegenseitigen Referenzen.
Man stelle sich vor, das Moterinterface würde das Interface der Liste kennen, in der es steht.
Jetzt entfällt von außen der letzte Zugriff auf die Liste und der RefCounter wird reduziert und läuft auf 0.
Die Liste wird automatisch freigegeben...
...wäre schön, aber klappt nicht, da das MotorInterfaceobjekt in der Liste ja eine Referenz auf die Liste hält.
Der RefCounter der Liste kann also durch Maßnahmen von außen nur auf 1 fallen. Also wird die Liste nie aufgelöst.
Auch das MotorInterfecedObjekt wird nie aufgelöst, da es ja von der Liste referenziert wird.
In solchen Fällen muss man dann selbst für das Aufräumen sorgen.
Man könnte für die Liste Clear aufrufen, wodurch die Referenzen auf die Items entfallen und auch die Referenz des Motors auf die Liste entfällt und alles freigegeben wird.
Dies braucht aber eine explizite Veranlassung.
Dennoch ist die Referenzzählung eine ganz angenehme Handhabung. Man muss aber berücksichtigen, dass es insgesamt ein anderes Handling ist.
Man kann allerdings auch Interface-Objekte ohne Referenzzählung verwenden.
Dann bestimmt man die Lebenszeit der Objekte weiterhin selbst, kann aber Objekte weiterhin leichter austauschen.
Das währe dann ähnlich einem Cast zu sehen.
Wenn man klassisch verschiedenste Objekte einer Methode übergeben will
Delphi-Quellcode:
uses
...alle Units, die ein fahrbares Objekt deklarieren...
procedure Fahre(O: TObject)
begin
if (O is TAuto) then
(O as TAuto).Fahre;
if (O is TFahrrad) then
(O as TFahrrad).Fahre;
end;
müsste man in der Methode das Objekt in TFahrrad oder TAuto casten.
Mit Interfaces könnte man folgendes deklarieren:
Delphi-Quellcode:
uses
MyInterfaces;
procedure Fahre(FahrbaresObjekt: IKannFahren)
begin
FahrbaresObjekt.Fahre;
end;