Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Tutorials und Kurse (https://www.delphipraxis.net/36-tutorials-und-kurse/)
-   -   Interfaces + Factorys (https://www.delphipraxis.net/183702-interfaces-factorys.html)

stahli 29. Jan 2015 19:06

Interfaces + Factorys
 
Liste der Anhänge anzeigen (Anzahl: 1)
Ich habe länger gebraucht, um das Konzept und die Benutzung von Interfaces in Delphi zu verstehen.

Es gibt zwar einige Erklärungen und Beispiele, die waren jedoch nicht immer für Neulinge in dem Themenbereich geeignet.

Daher habe ich hier einmal versucht, meine Erkenntnisse und bisherigen Erfahrungen zusammenzufassen.
Die Videos sind nicht hochprofessionell, aber vielleicht helfen sie einigen Interessierten, die Interfaces in Delphi schneller zu verstehen und anwenden zu können...


Die Erläuterungen habe ich in 3 Blöcke aufgeteilt:

Einige Basics zu Interfaces in Delphi.
http://youtu.be/MEX1836SvgE

Etwas detaillierte Infos und Anwendungsbeispiele.
http://youtu.be/IyvqZAUSJGY

Verbergen der Klassen und Arbeiten nur mit Interfaces.
http://youtu.be/NcbIV0SUFJs


Wenn ich etwas fehlerhaft oder unvollständig erläutert habe, dann können wir das hier gern richtig stellen... ;-)

alda 29. Jan 2015 21:49

AW: Interfaces + Factorys
 
Ich widerspreche gleich mal der Aussage in Video Teil 3, ab Minute 14:00, wenn ich darf :P.
Der "Aufwand" den Du ansprichst ist lediglich ein Manko der IDE. Die Vorteile kommen Dir immer dann zu Gute, wenn Du Implementierungen temporär (mehrfach) austauschen musst. Da wirst Du also dann drauf stoßen, wenn Du alles akribisch testest.

Es würde anderen mit Sicherheit auch sehr helfen, wenn Du in diesem Zuge (langfristig) auch das Thema Testing (Testdriven vs. nicht Testdriven vs. keine Interfaces) mit aufnimmst.

Weiter so und noch viel Spaß :-)

Mavarik 29. Jan 2015 22:59

AW: Interfaces + Factorys
 
hmm..

Zu Video 1:

Klar Funktioniert das... Aber so etwas wäre auch so gegangen:

Delphi-Quellcode:
type
   TBaseReader = class
     public
       Procedre Read;virtual;Abstract;
   end;

   TXMLReader = class(TBaseReader)
     public
       Procedure Read;Virtual;Override;
   end;

   TCSVReader = class(TBaseReader)
     public
       Procedure Read;Virtual;Override;
   end;

implementation

Procedure Read(Reader : TBaseReader);
begin
  Reader.Read;
end;


procedure TForm31.FormCreate(Sender: TObject);
var
 R1 : TXMLReader;
 R2 : TCSVReader;
begin
  R1 := TXMLReader.Create;
  R2 := TCSVReader.Create;
  Read(R1);
  Read(R2);
  R1.Free;
  R2.Free;
end;
Schau Dir mal das an...

Mavarik

Dejan Vu 30. Jan 2015 06:57

AW: Interfaces + Factorys
 
Abstrakte Klassen ohne Funktionalität sind den Interfaces ähnlich, nur das sie eben Delphi sind und bleiben. Ein Interface kommt von irgendwo her, definiert einen Vertrag und wenn deine Implementierung den erfüllt, kannst Du eine Delphi-Klasse an ein PHP-Skript hängen, oder eine C-Implementierung deines Interfaces in deinem Programm. Mach das mal mit deinen abstrakten Klassen.

Als Anwender bzw. 'Implementierer' weiß ich nicht, ob die abstrakte Klasse nicht doch eine Grundfunktionalität implementiert oder implementieren wird. Bei einem Interface kann ich mir 100% sicher sein, das alles, was passiert, in meinem Code stattfindet.

OlafSt 30. Jan 2015 09:18

AW: Interfaces + Factorys
 
Es geht doch auch gar nicht darum, ob und wie man das mit Klassen hätte machen können. Solange man innerhalb von Delphi unterwegs ist, kann man alles mit Klassen erschießen und kann auf Interfaces mehr oder weniger verzichten.

Aber das ist nicht der Punkt.

stahlis Videos dienen dazu, Typen wie mir überhaupt begreiflich zu machen, wie Interfaces und das ganze Drumherum in Delphi überhaupt funktioniert. Ich mußte mich erst als totaler Nixblicker outen und einen Thread erstellen :oops: Mir hat das Video Nummer 1 klar gezeigt, das ich die Basics nun verstanden habe und die beiden anderen Videos zeigen praktische Anwendungen auf extrem niedrigen Niveau - aber genau dieses Niveau muß man haben. Denn nur dann kapiert auch einer, der es gerade erst vor 5 Minuten gerafft hat, auch wirklich.

Ich hab auch gleich einen Kandidaten gefunden, der sich diese Videos mal reinziehen sollte.

Mavarik 30. Jan 2015 10:06

AW: Interfaces + Factorys
 
Zitat:

Zitat von Dejan Vu (Beitrag 1288218)
Abstrakte Klassen ohne Funktionalität sind den Interfaces ähnlich, nur das sie eben Delphi sind und bleiben. Ein Interface kommt von irgendwo her, definiert einen Vertrag und wenn deine Implementierung den erfüllt, kannst Du eine Delphi-Klasse an ein PHP-Skript hängen, oder eine C-Implementierung deines Interfaces in deinem Programm. Mach das mal mit deinen abstrakten Klassen.

Darum geht es doch gar nicht... Ich finde nur es ist ein schlecht Beispiel um Interfaces zu erklären.
Der Programmieren sollte doch auf den 1. Blick sehen warum er damit arbeiten soll und nicht so "altklug" wie ich kontern können : na nana na naaaa naaaaaa... Kann ich auch ohne Interfaces...
So ist das keine Motivation.

Zitat:

Zitat von OlafSt (Beitrag 1288245)
Ich hab auch gleich einen Kandidaten gefunden, der sich diese Videos mal reinziehen sollte.

Dann schau Dir lieber mal das Video an!

Nach diesem Video habe ich mich SOFORT auf Interfaces eingestellt... Genau aus den 2 wichtigen Gründen.

- Entkoppelung
- Referenzzählung
- Shoot & Forget Code

Somit schlage ich zwei Fliegen mit einer Klappe... Meine "objecte" sind ARC kompatible und mein Code ist unabängig von einander.

Zum Beispiel wenn ich Funktionalitäten an und abschalten will...

Delphi-Quellcode:
var
  NeueRoutine : INeueRoutine;
begin
  NeueRoutine := GlobalClassManager.GetClass<INeueRoutine>;
  if Assigned(NeueRoutine)
    then NeueRoutine.MachwasTolles
    else MyMessage('Funktionalität in der Lite Version nicht enthalten');
end;
Jetzt kann ich entweder per Programmlogik das Interface nicht am Pool registrieren... oder die Unit ist ggf. gar
nicht im Projekt enthalten, denn in der Unit steht:

Delphi-Quellcode:
Initialization
  GlobalClassManager.RegisterClass<INeueRoutine,TNeueRoutine>;
oder eben per Logic

Delphi-Quellcode:
Initialization
  if Lizenz.Can_NeueRoutine then
    GlobalClassManager.RegisterClass<INeueRoutine,TNeueRoutine>;
Für die Implementation gibt es 2 Wege... (Wahrscheinlich hunderte)

Entweder

Delphi-Quellcode:
Unit neueRoutine;

Interface
type
   INeueRoutine = Interface
     Procedure MachWasTolles;
   end;
Implementation
type
   TNeueRoutine = Class(TInterfacedObject,INeueRoutine)
                   private
                     Procedure MachWasTolles;
                  end;  
...
So mach Ich es... Bedeutet jedoch auch wenn ich das Interface nicht am Pool Registriert habe... Die Unit ist wegen dem Interface immer dabei.

oder
Delphi-Quellcode:
Unit MyInterfaces;

Interface

type
   INeueRoutine = Interface
     Procedure MachWasTolles;
   end;

Implementation

end.

// ---------------------------------------

Unit NeueRoutine;

Interface
// NIX
Implementation

Uses ClassManager,MyInterfaces;

type
   TNeueRoutine = Class(TInterfacedObject,INeueRoutine)
                   private
                     Procedure MachWasTolles;
                  end;  
...
Der Vorteil vom 1. Weg liegt auf der Hand... Ich kann dem Kunden einfach einen neuen Registrierungscode zusenden und schon kann er das Programmteil nutzen. (InApp-Käufe)

Der 2. Weg macht die Exe kürzer (Wenn das "neue" nicht dabei ist).

Naja und auf jeden Fall kann ich die Klasse unter ARC (Mobile) genauso verwenden wie unter OSX/Windows...

3. Grund Shoot & Forget

Delphi-Quellcode:
Var
  AndererCode : IAndererCode;
begin
  AndererCode.MachwasUndWegDamit; // Class Function Return IAndererCode;
end;
Mavarik

stahli 30. Jan 2015 10:28

AW: Interfaces + Factorys
 
@Mavarik

Ich bin ein Freund der kleinen Schritte und wollte zunächst einmal aufzeigen, wie und wozu man von der bisherigen Arbeit mit normalen Klassen zu Interfaces wechseln kann.

Dependency Injection und das Spring Framework sind sicher nicht hilfreich, um Interface-Neulingen ein Grundverständnis zu vermitteln.

Es ist ja nicht ausgeschlossen, dass man mit einiger Erfahrung dann den Schritt gehen will, aber das Thema ist hier deutlich verfrüht (und wäre besser in einem eigenen Thread aufgehoben).

Dejan Vu 30. Jan 2015 11:49

AW: Interfaces + Factorys
 
Zitat:

Zitat von Mavarik (Beitrag 1288253)
Zitat:

Zitat von Dejan Vu (Beitrag 1288218)
...Mach das mal mit deinen abstrakten Klassen.

Darum geht es doch gar nicht...

Äh doch.

Grundkurs Taschenrechner:
Lehrer: "Wir tippen [1] [+] [1] [=] und haben das Ergebnis"
Schüler: "Wieso tippen? Das rechne ich im Kopf aus".

G-r-u-n-d-k-u-r-s

... Verstehst Du?

Mavarik 30. Jan 2015 13:41

AW: Interfaces + Factorys
 
Zitat:

Zitat von Dejan Vu (Beitrag 1288273)
G-r-u-n-d-k-u-r-s

... Verstehst Du?

Motivation
... Verstehst Du?

Ein G-r-u-n-d-k-u-r-s der mir sagt..
- boh viel tipperei
- boh aufwending

Und dann eigentlich kein Lernziel nennt...

Mavarik

Stevie 30. Jan 2015 14:50

AW: Interfaces + Factorys
 
Zitat:

Zitat von Mavarik (Beitrag 1288253)
*snip*

Argh, Service Locator Antipattern. Den Code, den Nick da irgendwann mal gezeigt hat, muss ich den Leuten heute noch immer wieder austreiben. :stupid:

Bitte bitte. Auch wenns auf den ersten Blick clever auschaut, seine Software so zu entkoppeln, macht man es auf lange Sicht nur viel schlimmer.
Und außerdem ist das keine Dependency Injection. Du injektest da nix, sondern greifst über einen SL auf eine an anderer Stelle registrierte Instanz zu.

OlafSt 30. Jan 2015 21:54

AW: Interfaces + Factorys
 
Ihr seht ja selbst, wohin das führt. Ich als jemand, der man gerade eben geschnallt hat, wie das überhaupt funktioniert (bin noch immer erstaunt, das ich einen Klassenkonstruktor aufrufe, aber ein Interface bekomme), wird hier mit 3 Posts bereits total überfordert.

Spring4D ? Dependency Injection ?
Zitat:

Jetzt kann ich entweder per Programmlogik das Interface nicht am Pool registrieren
Wat fürn Pool denn ? Und was ist GlobalClassManager ? F1 bringt da nix zutage... Natürlich kann ich Google quälen, doch die Ergebnisse, die man dabei präsentiert bekommt, sind einfach vier Hausnummern zu hoch.

Versteht ihr ? Ihr redet hier von Dingen, die im 8. oder 9. oder auch erst 28. Schritt interessant werden. An die Leute, die die anderen 7,8,27 Schritte noch nicht gemacht haben, denkt ihr dabei nicht. Das ist vergleichbar mit dem Lerneffekt, den man hat, wenn man einem Anfänger, der gerade kapiert hat, was eine Schleife ist, versucht eine doppelt verkettete Liste zu erklären. Kann er nicht kapieren, weil zwischen "gerade Schleife verstanden" und "Zeiger auf Objekte" noch ein paar Schritte liegen, die man einfach nicht überspringen kann.

Ergo: Auf genau diese Leute, die ein paar Schritte noch nicht gemacht haben, auf diese zielt Stahli mit seinen drei Videos. Und ich bin ausgesprochen neugierig, wie das wohl weitergeht. Besonders spannend ist es doch, wenn wir an das Thema Daten herankommen.

Ich denke da noch immer an meine Dekoder-Klassen. Spätestend, wenn ich denen irgendeinen Binärkrams zu futtern gegeben habe und die dekodierten Daten bereitstehen, muß ich da ran. Geht sowas und wenn ja, wie ? Und irgendwann sind wir dann so weit, das wir sowas in eine DLL stopfen. Und noch etwa später soweit, das wir diese DLL auch mit C# in .NET-Umgebungen benutzen können.

Meinetwegen dürfen das gerne weiter Enten und Adler und Boote sein - ob diese Art Klassen Sinn machen oder nicht, ist für das Tutorial und das Verstehen echt sensationell unwichtig, solange man die Mechanik kapiert.

stahli 30. Jan 2015 23:09

AW: Interfaces + Factorys
 
Hallo Olaf, freut mich, dass sich noch jemand auf meiner Ebene herum treibt... :-)

Ich will mal noch ein paar Dinge aufgreifen:


@alda

Die Testbarkeit von Klassen wird durch Interfaces erleichtert und eigentlich erst richtig ermöglicht. Das habe ich verstanden.
Man kann also echte Funktionalitäten (z.B. komplizierte Berechnungen oder Datenbeschaffungen) mal schnell durch eine Dummyklasse ersetzen, die mal schnell ein paar statische Testdaten bereitstellt.
Wenn die echte Klasse und die Dummyklasse die gleiche benötigte Schnittstelle unterstützen kann man sie ja einfach mal schnell austauschen.

IDateninterface := TEchteKlasse.Create;
oder
IDateninterface := TDummyKlasse.Create;

Ansonsten funktioniert alles andere unverändert.
Mehr als diese grobe Zusammenfassung kenne ich selbst aber nicht.


@Mavarik
Du hast Recht mit Deiner Bemerkung zur Alternative "Basisklasse".
In meinem ersten Videoversuch bin ich darauf auch noch eingegangen (den habe ich aber aus bestimmten Gründen entsorgt ;-)) und im zweiten habe ich das dann vergessen. Aber zumindest habe ich ja erläutert, dass beide Klassen unabhängig voneinander sind. Das Beispiel bezieht sich also auf Fälle, wo es keine gemeinsame Basisklasse gibt.


@OlafSt

Also auf die Daten bin ich nur aus Zeitgründen nicht eingegangen (und weil ich es schon für so logisch und normal hielt ;-))
Propertys, Getter und Setter definiert man im Interface genau so wie in der Klasse (man muss aber alles komplett definieren - es reicht also nicht, nur "property Value: Integer;" zu schreiben und in der Klasse dann zusätzlich Getter und Setter zuzuweisen).
In der Klasse wird dann natürlich i.d.R. noch ein privates Feld "fValue: Integer" eingeführt, wovon das Interface nichts weiß und nichts wissen soll.
Auf den Wert kannst Du dann ganz normal über IMyInterface.Value zugreifen. Der tatsächliche Speicherung des Wertes in fValue oder im Internet oder einer Datenbank regelt dann (im Verborgenen die Klasse).
Das ist gerade ein schönes Beispiel für den Testfall oben: Die echte Klasse holt den Wert vielleicht aufwendig aus dem Internet und eine vorläufige Testklasse liefert ihn vorläufig mal schnell eben statisch zurück.

Mit dem Einbinden von DLL´s habe ich mich noch nicht befasst. Aber wenn man das tut und z.B. einer Schnittstelle IXyz benutzt (mit einer zugewiesenen GUID) dann könnte in der DLL auch eine Klasse mit dieser Schnittstelle (gleiche GUID) bereitgestellt und vom Projekt benutzt werden.
Heute haben wir TVerbrennungsmotor und TElektromotor und in 5 Jahren kann dem Projekt von außen noch ein TWarpAntrieb bereitgestellt werden wenn er die gleiche Schnittstelle implementiert.
Ich kenne das aber auch nur bis hierher und näheres weiß ich nicht - auch nicht, ob Delphi-Schnittstellen irgendwie von .NET verwendet werden können (oder anders rum).


@all: Mehrfachvererbung und Interfaces

Falls jemand einen Account bei Video2Brain hat oder sonst an das Video kommt möchte ich https://www.video2brain.com/de/video...rosse-training von Mirko Matytschak empfehlen.
Das Tutorial betrifft zwar C# aber die Beiträge
- Mehrfachvererbung und
- Schnittstellen
sind sehr allgemein gehalten und sehr informativ.

stahli 30. Jan 2015 23:33

AW: Interfaces + Factorys
 
Liste der Anhänge anzeigen (Anzahl: 1)
Mal noch ein Versuch, die Beziehung Objekt <-> Schnittstellen zu verbildlichen:

- es wird immer zunächst ein OBJEKT erzeugt
- es wird auch immer mit dem (allerdings nicht komplett sichtbaren) OBJEKT gearbeitet
- das Objekt kann mehrere Interfaces implementieren
- alle Interfaces sind Teil des Objektes.
- entsprechend können die Interfaces auch auf die privaten Bereiche und Daten des Objektes zugreifen

- eigentlich SIND die Interfaces das Objekt
- man kann das als eine Art casting ansehen: z.B. SchnittstelleA(Objekt), SchnittstelleB(Objekt) oder SchnittstelleC(Objekt)

- somit kann man ermitteln, ob Objekt InterfaceA implementiert oder das InterfaceC das InterfaceB
- entsprechend kann man dann auch das Interface(Objekt) in InterfaceA casten oder das InterfaceC in InterfaceB

- immer wenn eine Schnittstelle verwendet wird, ist damit letztlich das Objekt dahinter genutzt, aber mit einer Art Maskierung

Bei TInterfacedObject-Ableitungen kommt noch hinzu, dass der Compiler bei jeder ObjektAlsInterfaceX-Verwendung einen Zähler hochzählt (im Bild 1..5) und beim verlassen des Scopes oder Zuweisung eines anderen Objektes (oder NIL) an die Interface-Variable den Zähler wieder verringert. Kommt der Zähler bei 0 an, wird das Objekt freigegeben.

Bei Benutzung eines Interfaces benutzt man also das Objekt, jedoch mit einer eingeschränkten Sicht und einer etwas anderen Verhaltensweise.

Wegen dieser automatischen Referenzzählung und der automatischen Freigabe des Objektes bei "Zähler = 0" sollte man unbedingt vermeiden, das Objekt mal als Objekt und mal als Interface zu benutzten. Auch die Lebenszeitverwaltung über einen Owner wäre fatal.
Entweder das Objekt ganz normal verwenden und dieses später freigeben oder dieses nach dem Create nur noch über Interfaces nutzen und den Lebenszyklus automatisch regeln lassen.


Olaf, entschärft das die Mystik etwas?

Harry Stahl 31. Jan 2015 00:54

AW: Interfaces + Factorys
 
Also mir haben die Videos gefallen, ich finde das ist ein guter Einstieg ins Thema. Sicher kann man noch mehr mit Interfaces machen, aber wenn man da direkt die komplexen Sachen mit erklären würde, wäre es eben kein Einstieg mehr.

Jedenfalls wurden grundlegende Funktionalitäten gut und nachvollziehbar dargestellt. Da ich selber weiß, wieviel Arbeit mit solchen Videos vebunden ist, kann ich nur sagen, vielen Dank Stahli.

Freut mich zudem, das Delphi auch noch weiterhin ein Thema für Dich ist.
:thumb::thumb:

OlafSt 31. Jan 2015 10:17

AW: Interfaces + Factorys
 
Zitat:

Zitat von stahli (Beitrag 1288382)
Hallo Olaf, freut mich, dass sich noch jemand auf meiner Ebene herum treibt... :-)

ich schätze mal, in Sachen Interfaces bin ich kaum vom Boden hochgekommen ;) Aber ich lerne gern und schnell dazu.

Zitat:

Also auf die Daten bin ich nur aus Zeitgründen nicht eingegangen (und weil ich es schon für so logisch und normal hielt ;-))
Propertys, Getter und Setter definiert man im Interface genau so wie in der Klasse (man muss aber alles komplett definieren - es reicht also nicht, nur "property Value: Integer;" zu schreiben und in der Klasse dann zusätzlich Getter und Setter zuzuweisen).
Ich habe mal etwas gegoogelt und bin über einen alten Thread gestolpert. Da gehts dann so:

Delphi-Quellcode:
type
  ITest = interface
    function GetMyProp: Integer;
    procedure SetMyProp(Value: Integer);
    property MyProp: Integer read GetProp write SetProp;
  end;
Nun, in obigem Interface sind nun logischerweise Getter (GetMyProp), Setter (SetMyProp) und das Property selbst
Delphi-Quellcode:
public
. Ich kann die Methoden nicht verstecken, ergo können sie direkt aufgerufen werden. Wozu sind dann Properties überhaupt sinnvoll in Interfaces ? Darüber nachgedacht ist die Frage vermutlich genauso sinnvoll wie die Frage "Was war vor dem Urknall"... Aber lassen wir die Fachleute sprechen.

Zitat:

Mit dem Einbinden von DLL´s habe ich mich noch nicht befasst.
Dann hau mal rein n Schlag ;) Programmierer lernen nie aus ;)

Dejan Vu 31. Jan 2015 10:18

AW: Interfaces + Factorys
 
Zitat:

Zitat von OlafSt (Beitrag 1288393)
Nun, in obigem Interface sind nun logischerweise Getter (GetMyProp), Setter (SetMyProp) und das Property selbst
Delphi-Quellcode:
public
. Ich kann die Methoden nicht verstecken,

Doch. :mrgreen: Machs einfach.

stahli 31. Jan 2015 10:34

AW: Interfaces + Factorys
 
Liste der Anhänge anzeigen (Anzahl: 3)
Zitat:

Zitat von OlafSt (Beitrag 1288393)
Nun, in obigem Interface sind nun logischerweise Getter (GetMyProp), Setter (SetMyProp) und das Property selbst
Delphi-Quellcode:
public
. Ich kann die Methoden nicht verstecken, ergo können sie direkt aufgerufen werden. Wozu sind dann Properties überhaupt sinnvoll in Interfaces ?

Ich würde es so zusammenfassen: Die Klasse muss Getter und Setter haben, die müssen aber nicht öffentlich sein.
Bei Benutzung des Interfaces ist dann nur das Property erreichbar.

Ich bin darüber auch immer etwas stutzig geworden weil ja das Interface dann ggf. doch nicht vollständig benutzbar ist. Aber letztlich ist das ganz gut so.

Man muss halt schauen, dass die Klasse alle notwendigen Methoden auch wirklich öffentlich macht.


KORREKTUR:

Ich habe eben gesehen, dass meine Vögel die Methode FLIEG öffentlich deklariert haben, die Ente SCHWIMM aber versehentlich privat (hatte ich einfach nicht aufgepasst).
Benutzen konnte ich aber dennoch beide Methoden. Lediglich die Codevervollständigung hat die private Methode nicht angezeigt.
Also wären die Getter und Setter wohl auch erreichbar, aber nicht so ganz offensichtlich.

Sir Rufo 31. Jan 2015 10:53

AW: Interfaces + Factorys
 
Nur mal so zum Nachdenken:

Ich habe eine
Delphi-Quellcode:
public
Property wo der Getter und Setter jeweils
Delphi-Quellcode:
private
deklariert sind.

Kann man dort eigentlich wirklich von
Delphi-Quellcode:
private
sprechen, denn der Zugriff ist eben über die Property direkt möglich. Ist es nicht eher aus kosmetischen Gründen so gedacht, diese Getter/Setter als
Delphi-Quellcode:
private
zu deklarieren, damit ich in der Codevervollständigung nicht das hier sehe
Delphi-Quellcode:
.Foo
.GetFoo
.SetFoo
Und was passiert bei einem Interface? Exakt das Gleiche, da werden die Getter/Setter nicht angezeigt. In der Codevervollständigung wird mir ausschließlich die Eigenschaft angezeigt, die Getter/Setter sind nicht direkt erreichbar.

DeddyH 31. Jan 2015 10:56

AW: Interfaces + Factorys
 
IIRC stimmt das so nicht ganz. Wenn man das Interface nutzt, zeigt zwar die Codevervollständigung Getter und Setter nicht an, sie lassen sich aber trotzdem direkt aufrufen. Das ist zwar Quark, aber wohl nicht zu ändern.

stahli 31. Jan 2015 10:58

AW: Interfaces + Factorys
 
Ihr habt beide Recht. Habe ich gerade auch noch festgestellt und korrigiert.

Sir Rufo 31. Jan 2015 15:05

AW: Interfaces + Factorys
 
Zitat:

Zitat von DeddyH (Beitrag 1288400)
IIRC stimmt das so nicht ganz. Wenn man das Interface nutzt, zeigt zwar die Codevervollständigung Getter und Setter nicht an, sie lassen sich aber trotzdem direkt aufrufen. Das ist zwar Quark, aber wohl nicht zu ändern.

Und was ist technisch gesehen der Unterschied zwischen dem impliziten und expliziten Aufruf von Getter und Setter?

Aus welchen technischen Gründen werden bei Klassen die Getter/Setter idR nicht public deklariert?

stahli 31. Jan 2015 15:24

AW: Interfaces + Factorys
 
Technisch gesehen ist das natürlich gleich.
Aber wenn man sich erst neu mit Interfaces befasst kann halt etwas undurchsichtig sein und Einsteiger verstehen die Zusammenhänge nicht direkt.

Wenn man erst mal verstanden hat wie es läuft, dann kommt man damit allerdings zurecht - geht jedenfalls mir so.
Jedenfalls weiß ich jetzt, dass ich Getter und Setter deklarieren muss, mir die Codevervollständigung diese aber nicht andauernd unter die Nase reibt wenn sie privat sind.

Damit kann ich leben.

Sir Rufo 31. Jan 2015 15:28

AW: Interfaces + Factorys
 
Das mit dem Getter/Setter muss ich doch schon ohne Interfaces verstanden haben und warum diese idR nicht als public deklariert werden. Dann versteht man auch warum es schnurz ist, wenn man doch darauf zugreifen kann (weil das technisch bedingt bei Interfaces so ist und nicht anders möglich ist).

DeddyH 31. Jan 2015 15:28

AW: Interfaces + Factorys
 
Wofür braucht man technisch gesehen überhaupt Sichtbarkeiten?

Sir Rufo 31. Jan 2015 15:30

AW: Interfaces + Factorys
 
Zitat:

Zitat von DeddyH (Beitrag 1288421)
Wofür braucht man technisch gesehen überhaupt Sichtbarkeiten?

Bei einer public Property werden die Getter/Setter nur aus kosmetischen Gründen nicht public deklariert, eben aus dem gleichen Grund warum die Codevervollständigung bei Interfaces die Getter/Setter nicht anzeigt.

stahli 1. Feb 2015 11:00

AW: Interfaces + Factorys
 
Jetzt muss ich noch etwas zum Video 2 + 3 nachfragen (Stand hochgeladenes Projekt vom ersten Beitrag).

Ich habe zwei Fahrzeuge, die einen Motor haben (Auto und Boot).
Jetzt könnte man sich noch ein Elektrofahrrad einführen, hätte also 3 Klassen mit einem Motor.

Wenn IMotor StarteMotor und Tanken implementiert, müssen das die 3 Klassen auch unmittelbar tun.
Auto und Boot könnten aber mit einem Benzin-, Diesel- oder Elektromotor ausgestattet werden.

Wäre es somit nicht vielleicht sinnvoll, eine Klasse TMotor UND dazugehörige Schnittstelle IMotor einzuführen?

Dann könnten die Fahrzeuge so aussehen:

Bsp. Auto:
Delphi-Quellcode:
  TAuto = class(TInterfacedObject, IHasMotor)
  private
    FMotor: IMotor;
      FAnzahlRaeder: Integer;
      procedure SetAnzahlRaeder(const Value: Integer);
      procedure Fahre;
    //procedure StarteMotor;
    //procedure Tanken;
  public
    property Motor: IMotor read get_Motor write set_Motor;
      property AnzahlRaeder: Integer read FAnzahlRaeder write SetAnzahlRaeder;
  end;
Die Interfaces könnten dann so aussehen:

Delphi-Quellcode:
  IMotor = interface
    procedure StarteMotor;
    procedure Tanken;
  end;

  IHasMotor = interface
    function get_Motor: IMotor;
    procedure set_Motor(const Value: IMotor);
    property Motor: IMotor read get_Motor write set_Motor;
  end;
So könnte man dem Auto verschiedene Motoren hinzufügen.
Allerdings könnte ein Elektromotor dann nicht tanken sondern man würde die Batterie aufladen.
Ich muss also zur Laufzeit ggf. immer prüfen, welcher Motor eingebaut ist und ob überhaupt einer existiert.

Klar, man kann der Fabrik schon sagen: Gib mir ein Auto mit Elektromotor zurück, aber zur Laufzeit weiß man ja dann dennoch nicht, was man nun genau vorliegen hat.

An der Stelle komme ich nicht ganz weiter.
Wie würdet Ihr damit umgehen? (Aber bitte nicht auf das Spring-Framework o.ä. verweisen, damit das hier nachvollziehbar bleibt.)

Sir Rufo 1. Feb 2015 11:14

AW: Interfaces + Factorys
 
Mal ganz weg von der Programmierung, aber das Betanken an und für sich im Sinne von "fülle das nach, damit der Motor arbeiten kann" ist doch für jeden Motor in diesem Sinne gleich.

Das was man tankt muss zum Motor passen, aber "Tanken" müssen sie alle (vor allem weil "Tanken" das Befüllen eines "Tanks" -> Vorratsbehälters beschreibt ohne dabei das Tankgut oder den Tank irgendwie einzuschränken).

Also ist Tanken als Methode völlig ok (wenn das, was man tankt in diesem Kontext keine Rolle spielt bzw. spielen muss).

Einen konkreten Dieselmotor würde man dann mit einer Verbindung zu einem Diesel-Lieferanten erzeugen, den Benzinmotor mit einer Verbindung zu einem Benzin-Lieferanten und den Elektromotor mit einem Strom-Lieferanten. ;)

stahli 1. Feb 2015 11:33

AW: Interfaces + Factorys
 
Den Unterschied zwischen Tanken und Aufladen habe ich nur exemplarisch ins Spiel gebracht.
Es kann ja in echten Fällen möglicherweise Unterschiede geben, die für den weiteren Verlauf relevant sind.

Das ERZEUGEN eines Autos mit einem bestimmten Motor sehe ich nicht als Problem.
Aber später müsste ich immer prüfen:
- Kann das Objekt, das ich hier habe einem Motor haben?
- Hat es tatsächlich einen Motor?
- Kann ich damit tanken und was oder an die Steckdose?

Würdest Du also meine zwei Schnittstellen IMotor (die dann eine tatsächliche Motorinstanz representiert) und eine Schnittstelle IHasMotor (oder eigentlich besser: IKannMotorHaben) (die einem Auto und Boot hinzugefügt wird, nicht aber einem Fahrrad oder Vogel) sinnvoll finden?

Sir Rufo 1. Feb 2015 11:33

AW: Interfaces + Factorys
 
Man kann das mit dem Motor auch anders darstellen
Delphi-Quellcode:
IBetriebsstoffQuelle = interface
  function GetBetriebsstoffArt : Integer;
  procedure Entnehme( AMenge : Single );
end;

IMotor = interface
  procedure Tanken( AQuelle : IBetriebsstoffQuelle );
end;
Jetzt kann der Motor prüfen, ob die Quelle den richtigen Betriebsstoff liefert und sich die benötigte Menge entnehmen. ;)

Sir Rufo 1. Feb 2015 11:44

AW: Interfaces + Factorys
 
Zitat:

Zitat von stahli (Beitrag 1288450)
Den Unterschied zwischen Tanken und Aufladen habe ich nur exemplarisch ins Spiel gebracht.
Es kann ja in echten Fällen möglicherweise Unterschiede geben, die für den weiteren Verlauf relevant sind.

Das ERZEUGEN eines Autos mit einem bestimmten Motor sehe ich nicht als Problem.
Aber später müsste ich immer prüfen:
- Kann das Objekt, das ich hier habe einem Motor haben?
- Hat es tatsächlich einen Motor?
- Kann ich damit tanken und was oder an die Steckdose?

Würdest Du also meine zwei Schnittstellen IMotor (die dann eine tatsächliche Motorinstanz representiert) und eine Schnittstelle IHasMotor (oder eigentlich besser: IKannMotorHaben) (die einem Auto und Boot hinzugefügt wird, nicht aber einem Fahrrad oder Vogel) sinnvoll finden?

Das ist immer genau das Problem an diesen "an den Haaren herbeigezogenen" Beispielen, wo man Enten, Fahrräder und Autos irgendwie gleich behandeln will. Man kommt nicht so richtig ins vorne und stösst dabei auch eben auf die besagten Probleme.

Sinnvoller erscheint mir etwas wie
Delphi-Quellcode:
IMotorBetrieben = interface
  funtion GetMotor : IMotor;
end;

IMobil = interface
  procedure BewegeZu( Ort : TPoint );
end;

IKannSprechen = interface
  procedure Sage( AText : string );
end;
Mit
Delphi-Quellcode:
Supports
fragt man dann ab, ob dieses oder jenes Interface unterstützt wird und führt das dann aus:
Delphi-Quellcode:
procedure Sage( AText : string; AContext : IInterface );
var
  LSprecher : IKannSprechen;
begin
  if Supports( AContext, IKannSprechen, LSprecher ) then
    LSprecher.Sage( AText );
end;

stahli 1. Feb 2015 11:57

AW: Interfaces + Factorys
 
Ok, super!

Das IMotorBetrieben ist eine bessere Bezeichnung als IHasMotor. Aber konzeptionell meinte ich das Gleiche.

Allerdings reicht Supports(aContext, IMotorBetrieben) für einen Zugriff auf den Motor noch nicht ganz aus, weil der ja nil sein könnte.
Also muss dazu ggf. nochmal eine Prüfung her.

Wenn man ganz mutig denkt, könnte ein späterer Fluxkompensator natürlich zu unserer Motor-Schnittstelle nicht mehr kompatibel sein.
Aber ok, das muss man wohl akzeptieren und in 20 Jahren die Schnittstellen ggf. nochmal überarbeiten. ;-)


Die Enten und Fahrzeuge finde ich aber im Zusammenhang mit Schnittstellenbeispielen dennoch nicht schlecht, da man sieht, dass völlig unterschiedliche Klassen (ohne direkte gemeinsame Basisklasse) die gleichen Schnittstellen bedienen können.

Stevie 1. Feb 2015 12:00

AW: Interfaces + Factorys
 
Bei einigen Aktionen solltest du überlegen, ob sie wirklich zu der Klasse gehören.

Zum Beispiel die Methode Tanken gehört sicherlich nicht in die Klasse Motor oder Auto oder Boot.
Diese Klassen/Interfaces sind nicht die Akteure, sondern die Argumente für die Aktion betanken, welche ein IBetankbar Interface oder sowas haben.

Sir Rufo 1. Feb 2015 12:08

AW: Interfaces + Factorys
 
Wenn
Delphi-Quellcode:
nil
zulässig ist, dann hat man das Bei Google suchenNULL-Pattern oder man wirft idealerweise ASAP eine Exception (Guard).

Was richtig ist hängt vom Gesamtkontext ab und kann pauschal nicht beantwortet werden. Ich würde das aber zunächst völlig herauslassen und eben immer bei
Delphi-Quellcode:
IMotorBetrieben
einen Motor voraussetzen.

Sir Rufo 1. Feb 2015 13:05

AW: Interfaces + Factorys
 
Um Interfaces näher zu bringen würde ich eben ganz andere Beispiele nehmen, etwas, das man anfassen kann.

Wir haben z.B. eine Anwendung, die Dateien in einen Stream liest, die dann von der Anwendung weiterverarbeitet werden.

Dazu hat man sich eine schöne Klasse gebaut, die nun eine Datei in einen Stream einliest und diesen zurückgibt.
Delphi-Quellcode:
TFileStreamHandler = class
procedure GetStream( AFilename : string; AStream : TStream );
procedure WriteStream( AFilename : string; AStream : TStream );
end;
Vor allem ist das schick, wenn wir die Datei "$(Archiv1)\12345.doc" lesen wollen, setzt diese Klasse das gleich in den korrekten Pfad um :stupid: Sehr geschmeidig.

Delphi-Quellcode:
procedure Foo( AHandler : TFileStreamHandler );
var
  LStream : TStream;
begin
  AHandler.GetStream( '$(Import)\Bild.jpg', LStream );
  AHandler.WriteStream( '$(Archiv)\123456.jpg', LStream );
end;
Alles ist gut, bis zu dem Tag, wo man feststellt, dass es besser wäre, diese Streams nicht wirklich in Dateien, sondern in einer Datenbank zu halten. Kein Problem, ändere ich einfach die Klasse ... hmmm, wie machen wir das bloss. Diese Klasse benutzen wir an jeder Stelle im Programm für jede Datei, auch für die, die weiter als Datei dort liegen.

Ganz einfach mit Interfaces:
Delphi-Quellcode:
IStreamHandler = interface
  procedure GetStream( AFilename : string; AStream : TStream );
  procedure WriteStream( AFilename : string; AStream : TStream );
end;

// die alte Klasse tut es ja noch
TFileStreamHandler = class( TInterfacedObject, IStreamHandler )
procedure GetStream( AFilename : string; AStream : TStream );
procedure WriteStream( AFilename : string; AStream : TStream );
end;
Die Anwendung ändere ich um, damit die mit dem Interface arbeitet
Delphi-Quellcode:
procedure Foo( AHandler : IStreamHandler );
var
  LStream : TStream;
begin
  AHandler.GetStream( '$(Import)\Bild.jpg', LStream );
  AHandler.WriteStream( '$(Archiv)\123456.jpg', LStream );
end;
Jetzt benötigen wir eine neue Klasse, die uns den Dateiinhalt aus der Datenbank holt/schreibt
Delphi-Quellcode:
TDatabaseStreamHandler = class( TInterfacedObject, IStreamHandler )
procedure GetStream( AFilename : string; AStream : TStream );
procedure WriteStream( AFilename : string; AStream : TStream );
end;
Und jetzt noch eine, die den Zugriff routet
Delphi-Quellcode:
TRoutedStreamHandler = class( TInterfacedObject, IStreamHandler )
procedure GetStream( AFilename : string; AStream : TStream );
procedure WriteStream( AFilename : string; AStream : TStream );
end;
Und erzeuge die Klasse z.B. mit
Delphi-Quellcode:
function GetStreamHandler : IStreamHandler;
var
  LFiles : IStreamHandler;
  LDatabase : IStreamHandler;
begin
  LFiles := TFileStreamHandler.Create;
  LDatabase := TDatabaseStreamHandler.Create( 'Server1', 'user', 'pass', 'archivdb' );

  Result := TRoutedStreamHandler.Create( 
  {Default} LFiles,
  {} [
    {} 'Archiv', LDatabase,
    {} 'Archiv1', LDatabase ] );
end;
Welchen noch so ominösen Stream-Speicherort ich mir auch noch ausdenken werde (oder mir ausgedacht wird) ich erzeuge eine konkrete Klasse für diesen konkreten Ort und klatsche das in den Router mit rein. Die Anwendung arbeitet ohne weitere Änderungen wie gehabt weiter.

OlafSt 1. Feb 2015 18:55

AW: Interfaces + Factorys
 
Das Beispiel mit dem StreamHandler ist derart unverständlich, das ich empfehle, den Post zu entfernen. Das läßt Leute, die so unsicher auf dem Gebiet sind wie ich, gleich wieder an sich zweifeln.

stahli 1. Feb 2015 19:06

AW: Interfaces + Factorys
 
Den letzten Abschnitt verstehe ich auch nicht.

Dejan Vu 1. Feb 2015 19:12

AW: Interfaces + Factorys
 
Der StreamHandler definiert (so wie ich das verstanden habe) nur den Vertrag, das Daten mit einem Namen versehen in einen Stream geschrieben und aus ihm gelesen werden.

Wieso weiß der IStreamHandler eigentlich, das es Filenamen gibt? Sollte ihm das nicht komplett egal sein, ob es ein File-, Registry-, Tabellen-, oder Kleinkolonieseiteneingangs-namen handelt?

Du hast geschrieben, das ihr das erst später bemerkt habt. Klar, hinterher ist man immer schlauer. Daher als Tipp (für alle Leser): Beim Design von Klassen und Interfaces sollten nur gerade so viele Informationen in das Design gesteckt werden, wie die Klasse/das Interface benötigt.

Wenn also ein 'IStreamHandler' deklariert wird, reich es, einen 'Name' (oder treffender, falls ich das richtig verstanden habe: 'Identifier') anzugeben. Einfach deshalb, weil im Namen des Interfaces nirgends etwas mit 'File' steht.

Ansonsten halte ich das Beispiel für sehr anschaulich. Es ist auch ein tolles Beispiel, wie sehr man später davon profitiert, früher mal etwas richtig gemacht zu haben (der Name lässt sich ja leicht mit Refactoring noch zurechtrücken).

PS: Was der TRoutedStreamHandler macht, erschließt sich mir auch nicht.

Aber man kann z.B. einen Streamhandler für TCP schreiben, oder für eine INI-Datei, oder oder oder.

stahli 1. Feb 2015 22:50

AW: Interfaces + Factorys
 
Bin gerade noch auf eine kleine Eigenart gestoßen...

Folgendes kompiliert und funktioniert problemlos:
Delphi-Quellcode:
  IFlieg = interface
    ['{0E839812-DAB3-47F0-AF3E-AB05FFDD6CE6}']
    procedure Flieg;
    function get_X: Integer;
    procedure set_X(Value: Integer);
    property X: Integer read get_X write set_X;
  end;

  TVogel = class(TInterfacedObject, IFlieg)
  private
    fX: Integer;
  public
    procedure Flieg;
    function get_X: Integer;
    procedure set_X(Value: Integer);
    //property X: Integer read get_X write set_X;
  end;
Man kann also die Property in der Klasse weglassen (Getter bzw. Setter reichen) und kann die Property dennoch benutzen (auch die Codevervollständigung bietet sie an).

Ich denke mal, der besseren Lesbarkeit halber sollte man sie dennoch in der Klasse deklarieren.

Jumpy 2. Feb 2015 09:51

AW: Interfaces + Factorys
 
Zitat:

Zitat von Dejan Vu (Beitrag 1288492)
PS: Was der TRoutedStreamHandler macht, erschließt sich mir auch nicht.

Ich habe das so verstanden, dass TRoutedStreamHandler "entscheidet" welcher der tatsächlichen anderen Handler-Klassen (die ihm im OnCreate eingeflöst werden) die Anfrage nun bearbeitet. Wäre aber nett zu sehen, wie es bei ihm im Inneren aussieht, denn auch wenn ich glaube verstanden zu haben was er macht, weiß ich nicht wie.

Stevie 2. Feb 2015 10:00

AW: Interfaces + Factorys
 
Zitat:

Zitat von Jumpy (Beitrag 1288539)
wie es bei ihm im Inneren aussieht

Genau das ist der Punkt, wenn man mit Interfaces arbeitet.
Sofern man sich an die Regeln hält, dass die Implementierungen der Interfaces sich gemäß der Spezifikationen verhalten, ist es unerheblich, was sie intern machen.


Alle Zeitangaben in WEZ +1. Es ist jetzt 11:33 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz