Laden...

Für was ist ein Interface gut?

Erstellt von Frokuss vor 4 Jahren Letzter Beitrag vor 4 Jahren 1.466 Views
F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 4 Jahren
Für was ist ein Interface gut?

Wunderschönen guten Abend,

ich habe mich grade einmal versucht in Interfaces einzuarbeiten. Aber irgendwie fehlt mir hier das Verständis für... Hatte gedacht, dass mir das irgendwie weiterhelfen kann...

Hintergrund hierfür ist, dass ich eigentlich aus zwei Klassen erben will - was aber nicht geht. Ich habe zwei Klassen, die jeweils von anderen Control (Panel und Label) erben und beide teilweise identische Methoden und Attribute haben. Daher wollte ich das ganze nicht doppelt erzeugen...

Habe ich es also richtig verstanden, dass mit interfaces eigentlich nur für folgendes helfen:
1.) nicht vergessen vom implementieren von Attributen und Methoden
2.) zum iterativen durchlaufen einer solchen "Sammlung"

So sieht aktuell mein erster Versuch aus:
Interface:

    interface IListenElement {
        int ID { get; }
        string Typ { get; }
    }

eine Klasse:

    public class ListenElement : Label, IListenElement {
        readonly int id;        //eindeutige Kennung
        string typ;
        protected bool evt_mouseDown = false, evt_dragStarted = false;
        bool allowDrag = false;

        public ListenElement(int id, string typ) {
            this.id = id;
            this.typ = typ;
            this.TextAlign = ContentAlignment.MiddleLeft;
            this.Font = new Font(___Einstellungen.schriftart, ___Einstellungen.liste_textheight, ___Einstellungen.GetStyle());
        }

        public int ID {
            get { return id; }
        }

        public string Typ {
            get { return typ; }
        }
}

Sprich in meinem Fall bringt mir das eigentlich nur mehr arbeit 😄 Vielleicht kann ja jemand etwas Licht hinter die Sache bringen... Wäre super 😃

Gruß Frokuss

PS: zumindestens habe ich endlich mal herausgefunden wofür dieses große I im "Klassennamen" steht XD Dachte die ganze Zeit das steht für I = ich 😄

2.078 Beiträge seit 2012
vor 4 Jahren

Also zuerst einmal sollte dir klar sein, dass viele Konzepte nicht dafür da sind, dir in diesem Moment Arbeit zu ersparen, sondern in Zukunft 😉

Ein Interface ist im Grunde nur das Versprechen, dass eine Klasse die entsprechenden Member enthält - mehr nicht. Du kannst die Implementierung damit beliebig tauschen, die Arbeit hast Du pro Implementierung aber immer noch(, wobei VisualStudio dabei sehr helfen kann.

Besonders im Zusammenhang mit UnitTests sind Interfaces spannend, denn man kann sehr leicht Mock-Implementierungen bauen und anstelle der "echten" Implementierung verwenden. Bei richtigem Aufbau musst Du also keinen Code ändern um eine Komponente losgelöst vom Rest testen zu können, da Du die Abhängigkeiten dieser Komponente durch Mok-Implementierungen austauschen kannst. Stichwort: DependencyInjection

Außerdem bedeutet nur die Tatsache, dass ein paar Member von zwei Klassen identisch sind, nicht automatisch, dass sie auch eine gemeinsame Basisklasse oder ein gemeinsames Interface haben sollten, sondern nur, dass sie eben ähnlich sind.
Wenn Du beide Klasse parallel nutzen können möchtest, ohne dass dabei relevant ist, welche der Klassen gerade genutzt wird, dann könne ein Interface der richtige Weg sein.
Wenn Du die Klassen aber völlig unabhängig voneinander nutzt und nur Gemeinsamkeiten zusammenfassen möchtest, dann ist ein Interface vermutlich überflüssig.

Gemeinsamkeiten kannst Du auch auf andere Weise zusammenfassen. Komplexe Funktionen kannst Du z.B. in eine eigene Klasse auslagern und dann an den ursprünglichen Stellen nutzen. Hier könnte sich dann auch ein Interface für die Gemeinsamkeiten-Klasse lohnen, denn sowas will man vielleicht unit-testen können.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 4 Jahren

Okay, ich danke dir ersteinmal für deine Erläuterung 😃 Hat mir zumindestens etwas weitergeholfen.

Das bedeutet für meinen Anwendungsfall, dass ich sinnvoll sein kann. Denn ich möchte zwei verschiedene Controls erstellen, die theoretisch in beliebiger Reihenfolge parallel auf der gleichen Control-Ebene (daher Parent ist der gleiche Knoten) sein können. Da ich in meinem Fall diese am besten in einem gemeinsamen Dictionary aufnehmen möchte, wäre es also ratsam 😃

Danke dir 😃
Frokuss

PS: von Unit-Test bin ich noch super weit weg ^^

2.078 Beiträge seit 2012
vor 4 Jahren

Was Du beschreibst, klingt wie ein Panel?

Wenn Du dir die normalen Panels von WinForms anguckst, dann wirst Du fest stellen:
Da kann man auch massig Controls nebeneinander rein legen, allerdings erwartet das Panel nur eine "Control"-Ableitung - also praktisch alles.

Der entscheidende Punkt ist, dass bei WinForms das Panel sich nicht für die konkreten Daten der Controls interessiert, es teilt nur mit: "Zeichnet euch Mal neu" und fertig. Das Panel hat also keine Ahnung von seinen Children, es setzt nur voraus, dass sie sich selber zeichnen können.

Wenn das bei dir nicht reicht, weil dein Panel aus irgendeinem Grund die Ids wissen muss, dann kann das Interface helfen. Aber wie gesagt: Das klingt sehr stark danach, dass Du Schichten vermischst, die getrennt gehören.

Wenn Du deinen Controls nun ein Interface gibst, dann musst Du bedenken, dass die Objekte, nur weil sie das Interface implementieren, nicht automatische Controls sind.
Ich würde es daher anders herum machen, dass Du nur Controls erwartest, aber darauf prüfst, ob sie das Interface implementieren und dann damit umgehst, alle ohne das Interface bekommen ein Default-Verhalten.
Oder Du baust eine eigene Collection mit generischer Add-Methode und den generischen Parameter kannst Du für Basis-Klasse und Interface einschränken, dann ist egal, welchen Typen die intern haben, Du weißt immer, dass sie Control und IMyInterface sind.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 4 Jahren

Wenn das bei dir nicht reicht, weil dein Panel aus irgendeinem Grund die Ids wissen muss, dann kann das Interface helfen. Aber wie gesagt: Das klingt sehr stark danach, dass Du Schichten vermischst, die getrennt gehören.

Ich arbeite da aktuell mit diversen Events (unter anderem Drand&Drop)... Das bedeutet, jedes Label (Kind) darf nur maximal einmal im Parent (Panel) enthalten sein. Wenn es nicht vorhanden ist, wird eine Kopie des Label (Kind) gemacht. Jetzt kann es aber sein, dass sich ein Label (kind) ändert. Und dann soll sich jedes andere gleiche/geclonte Kind ändern. Ich habe daher mehrere Panels...
Das Panel verwaltet daher direkt, ob ich ein Kind überhaupt Droppen darf, oder nicht. Kenne mich aber noch nicht so gut mit den 3 Schichten aus... Achja... und die Kinder werden alphabetisch sortiert ^^

Wenn Du deinen Controls nun ein Interface gibst, dann musst Du bedenken, dass die Objekte, nur weil sie das Interface implementieren, nicht automatische Controls sind.
Ich würde es daher anders herum machen, dass Du nur Controls erwartest, aber darauf prüfst, ob sie das Interface implementieren und dann damit umgehst, alle ohne das Interface bekommen ein Default-Verhalten. Kannst du mir sagen, wie ich das anstelle?

Grüße Frokuss

2.078 Beiträge seit 2012
vor 4 Jahren

Viiiieeel zu kompliziert 😄 Es ist extrem schwer, komplexe Event-Strukturen übersichtlich zu verwalten und wenn Du nicht aufpasst, hast Du so spaßige Dinge, wie Memoryleaks.
Sei also vorsichtig mit Events, spätestens wenn Event-Handler weitere Event-Händer registrieren oder entfernen, wird's hässlich, oder wenn Events in beide Richtungen beobachtet werden.

Viel einfacher ist es, wenn Du Du die Daten in eigene eigene Klasse packst und das Label bekommt eine passende Instanz dazu. Die Kopie vom Label hat selbe keine Daten (nur UI-Zeug, wie z.B. Höhe/Breite) nimmt die selbe Instanz mit. Du brauchst pro Label also nur Event-Händler, die auf Änderungen von diesem Daten-Objekt horchen und wenn sich das Daten-Objekt ändert, wird jedes Label aktualisiert, die Labels wissen nichts voneinander.
Das ist (sehr oberflächlich beschrieben) das Konzept hinter MVVM, was bei WPF benutzt werden sollte. Das Daten-Objekt ist dann das ViewModel und das Label die View. Die View liest Daten vom ViewModel (bzw. schreibt sie zurück) und horcht auf Events, die mitteilen, dass das ViewModel sich geändert hat. Dank diesem System kann ich Mal eben so mein Label kopieren, lege ich eine TextBox dazu und ändere damit den Wert im ViewModel, wird jedes Label automatisch aktualisiert.
Das nennt sich DataBinding, WinForms hat auch ein System dafür, wenn auch nicht so gut, wie das von WPF.

Für die Sortierung gibt's verschiedene Möglichkeiten. Je nachdem, was Du genau machst, könntest Du z.B. die Reihenfolge der Quell-Liste, wo die Daten-Objekte her kommen, als führend betrachten und wenn sich diese Liste ändert, sortierst oder erstellst Du die Labels neu.
Oder Du bringst den Daten-Objekten bei, dass sie wissen, wie sie sortiert werden müssen (siehe IComparable) und sortierst die Labels dann anhand der Info vom Daten-Objekt.

Aber ja, das Panel darf ruhig entscheiden, was gedroppt werden darf und was nicht - wobei ich das nach MVVM auch wieder in ein eigenes ViewModel legen würde ^^

Kannst du mir sagen, wie ich das anstelle?

public Collection<Control> MyControls { get; }

public void DoSomething()
{
    foreach (var control in MyControls)
    {
        if (control is IMyInterface myControl) // Das ist ein Feature seit C# 7.x, genannt Pattern Matching
        {
            DoWithId(myControl.Id); // Interface ist implementiert
        }
        else
        {
            DoDefault(control); // Interface ist nicht implementiert
        }
    }
}
F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 4 Jahren

Cool danke 😃 Das sieht jetzt einfacher aus, als ich erwartet habe XD

Was das ganze mit meinen Events angeht... Ich versuche das einfach so handzuhaben, dass jedes Control nur die Events implementiert hat, die es aufgrund seiner eigenen Daten auch richtig verwalten kann. Sprich mein Label hat nur die Information, ob es gedragt werden darf oder nicht. Dies wird dann dort auch gehandhabt. Und zwar nur das.

        protected bool evt_mouseDown = false, evt_dragStarted = false;
        bool allowDrag = false;

        public ListenElement(int id, string typ) {
            this.id = id;
            this.typ = typ;
            this.TextAlign = ContentAlignment.MiddleLeft;
            this.Font = new Font(___Einstellungen.schriftart, ___Einstellungen.liste_textheight, ___Einstellungen.GetStyle());

            this.MouseDown += new MouseEventHandler(Evt_MouseDown);
            this.MouseUp += new MouseEventHandler(Evt_MouseUp);
            this.MouseMove += new MouseEventHandler(Evt_DragStart);
            this.DragEnter += new DragEventHandler(Evt_DragEnter);
        }

        public bool AllowDrag {
            get { return allowDrag; }
            set { allowDrag = value; }
        }

        private void Evt_MouseDown(object sender, MouseEventArgs evt) {
            if(allowDrag)
                evt_mouseDown = true;
        }

        protected virtual void Evt_DragStart(object sender, MouseEventArgs evt) {
            if(evt_mouseDown && !evt_dragStarted) {
//Anmerkung: Da ich verschiedene Daten habe, brauche ich "typ" um die Daten-Art zu erkennen und die ID um den Datensatz zu identifizieren
                string val = typ + Daten.Daten.StringTrennzeichen + id;
                this.DoDragDrop(val, DragDropEffects.Copy | DragDropEffects.Move);
                evt_dragStarted = true;
            }
        }
//...

Auch werden dort Events implementiert, die seine Form ändern. z.B. bei meiner Liste wird bei einem Klick (auf die Kopfleiste) die Liste Maxi- bzw. Minimiert.

Das ganze ist allerdings noch etwas komplizierter als hier, da ich innerhalb einer Liste noch weiteren Listen haben kann.

Aber wie gesagt: Wenn das Control mit seinen Daten das Event handhaben kann, dann ist es dort implementiert, ansonsten ist es in einem Parent-Node (oder im Parent des Parent des ... 😄 ) implementiert. Ganz oben steht dann ein Objekt, dass so gesehen auf alle Daten und Controls zugriff hat. Das ist bisher das einzige womit ich in meinem Projekt unzufrieden bin... Vermutlich weil es die Schnittstelle zwischen den Daten und der Oberfläche ist...

Aber auf jeden Fall: Danke 😃
Gruß Frokuss

EDIT:
kleinen Fehler in meinem Code gefunden... DoDragDrop läuft ja synchron ab...

        protected virtual void Evt_DragStart(object sender, MouseEventArgs evt) {
            if(evt_mouseDown) {
                string val = typ + Daten.Daten.StringTrennzeichen + id;
                this.DoDragDrop(val, DragDropEffects.Copy | DragDropEffects.Move);
                evt_mouseDown = false;//Hier ist DoDragDrop bereits abgeschlossen. Deswegen brauche ich die Variable zur Kontrolle gar nicht...
            }
        }
2.078 Beiträge seit 2012
vor 4 Jahren

Ich glaube, mir ist immer noch nicht ganz klar, was Du genau vor hast 😄
Vielleicht denke ich aber auch zu sehr im "WPF-Style", da geht man sowas völlig anders an.

Für DragDrop gibt's aber recht heufig ein kleines Konzept, wie man auch die Steuerung von der ZU raus ziehen kann. Ein kleines (für WPF gebautes) Framework, was ich Mal benutzt habe, ich Folgendes:
https://github.com/punker76/gong-wpf-dragdrop/tree/dev/src/GongSolutions.WPF.DragDrop

Wenn Du dir da die vier Interfaces anschaust, wird denke ich klar, was das Konzept ist.
Bei einem Element ist dann Drag/Drop erlaubt, wenn die jeweilige Daten-Klasse das passende Interface implementiert und die passende Can[...]-Methode true zurück gibt.

Somit kannst Du das DragDrop-Handling (was schnell sehr nahe an der Business-Logik sein kann) von der UI-Logik trennen. Das Control kümmert sich nur noch um das sichbare Zeugs (UI eben) und der Rest wird woanders erledigt.

Verstehe ich denn richtig, dass das deine ursprüngliche Frage war, wie Du das immer gleiche DragDrop-Handling zentralisieren kannst? Dann würde ich auf das oben angedeutete Beispiel zurück greifen. Vielleicht gibt's auch WinForms-Frameworks, die das vereinfachen, oder Du baust dir selber eins. Eventuell kann man dazu auch noch eine weiter Klasse bauen, deren Aufgabe ist, die Events behandeln.
Somit hast Du dann nur noch die Controls mit einem Daten-Objekt und eine Instanz der DragDropHandler-Klasse. Die horcht an den Events des Controls und verarbeitet sie anhand der Drag/Drop-Interfaces - sofern die Daten-Klasse sie implementiert. Stellt sich raus, der Drag/Drop-Versuch ist OK, dann teilt sie dem Control über z.B. ein eigenes Event mit: Eine Drag/Drop-Aktion wurde durchgeführt - wobei es dafür (meine ich) sowieso schon Events am Control gibt.

F
Frokuss Themenstarter:in
158 Beiträge seit 2015
vor 4 Jahren

Also 😄 Ich glaube nicht, dass du sehr kompliziert denkst... Denn das tue ich in der Regel... ;-D

Eigentlich wollte ich von zwei Klassen gleichzeitig erben. Was ja nicht geht. Deswegen dachte ich, dass da Interfaces helfen. Da in einem Interface allerdings nur public-Sachen sein dürfen, hilft das ganze mir nicht wirklich weiter... in dem anliegen. Es hilft mir aber dahingegen weiter, dass ich bestimmt Attribute (über get und set) und Methoden erzwingen kann.

Die Frage ist nun eher, warum ich das ganze wollte...
Ich erstelle mir grade eine "Liste" (Panels) die beliebig viele "ListenElemente" (Labels) enthalten kann. Jetzt wollte ich aber die möglichen Childs durch einen weiteren Control (ListenElementDetails) erweitern. Da jetzt aber ListenElement und ListenElementDetails vom gleichen Typ sein müssen, dachte ich mir, dass zumindestens das durch das Interface geregelt werden kann...

Aber... und das klingt jetzt unglaublich:

public Collection<Control> MyControls { get; }

ist schon die Lösung... ListenElement und ListenElementDetails sind beiden Controls... Ich §$%&/(
Ich habe vorher das verwendet:

SortedList<int, ListenElement> objElemente = new SortedList<int, ListenElement>();

Die Can[...]-Methode verwende ich quasi auch... heißt nur nicht so ^^ Aber was die Events angeht, werde ich einen neuen Thread (unter WinFOrms) aufmachen, da dies hier ja nichts zu suchen hat und noch mal ein anderes Problem enthält...

Bedeutet, eigentlich brauche ich nicht mal ein Interface (werde es aber trotzdem verwenen, um bestimmte Sachen zu erzwingen).

Super vielen lieben Dank 😃
Frokuss

5.941 Beiträge seit 2005
vor 4 Jahren

Hallo Frokuss

Abstrakte Klassen und auch ganz "normale" Klassen stellen auch eine Schnittstelle / Abstraktion dar, wie du jetzt mit "Control" gemerkt hast. Schau mal noch nach dem Stichwort "Polymorphie" und "Abstraktion".

Schau mal hier:

Zitat:

Wollen wir diese Abhängigkeit lockern / lösen, brauchen wir eine Abstraktion irgend einer Art.
Das kann ein Interface sein, eine Abstrakte Klasse oder auch eine einfache konkrete Oberklasse.

Vielleicht hilft dir das beim Verständnis.

Gruss Peter

--
Microsoft MVP - Visual Developer ASP / ASP.NET, Switzerland 2007 - 2011