Laden...

Warum immer gegen Interfaces programmieren?

Letzter Beitrag vor 18 Jahren 13 Posts 13.487 Views
Warum immer gegen Interfaces programmieren?

Hallo zusammen.

Ich war gerade in Laune und wollte mir schnell noch einmal klar machen, was es alles für Vorteile mit sich bringt, gegen Interfaces zu programmieren.

Gar nicht mal so einfach, da eine Zusammenfassung zu bekommen.
Weder in deutsch noch in englisch finde ich einen Artikel, der es mal auf den Punkt bringt.
Wäre es nicht Zeit für einen entsprechenden Artikel hier im Forum, zumal das ein immer wiederkehrendes Thema ist und der ein oder andere hier an Board auch den Satz "Programmiere immer gegen Interfaces" predigt?

Also nicht das WIE ist interessant, sondern das WARUM.
Damit man das mal wirklich verinnerlicht.

Ich muss leider immer mal wieder etwas nachlesen, weil mir alles mögliche im Kopf rumschwirrt, und dann habe ich mal wieder etwas vergessen und und und...

Deshalb mal meine Frage:

Warum sollte man immer gegen Interfaces programmieren?

Danke euch!

Hallo AtzeX,

Warum sollte man immer gegen Interfaces programmieren?

die Frage ist ganz leicht zu beantworten: Wegen der Entkoppelung von konkreten Klassen. Man kann ohne Änderungen im Client-Code die konkreten Klassen entfernen, austauschen oder neue hinzufügen, wenn man mit Interfaces arbeitet, solange das Interface selbst gleich bleibt.

Wäre es nicht Zeit für einen entsprechenden Artikel hier im Forum?

Was heißt Artikel? Aber es gibt zumindest Threads, in denen schon so ziemlich alles über Interfaces gesagt wurde, was es zu sagen gibt, z.B.

Interface, Klassen und Vererbung ?
Abstrakte Klasse vs Interface

herbivore

Schnittstellen

Man sollte immer dann gegen Schnittstellen programmieren, wenn man Teile einer Anwendung austauschbar haben will. Man definiert in einer Schnittstelle, was für Funktionen ein bestimmter Programmteil haben muss. Nun kann man verschiedene Ausprägungen dieses Programmteils schreiben, die alle diese Schnittstelle einhalten (Wie ein Vertrag). Den Aufrufer, der die Schnittstelle verwendet, kümmert sich nicht darum, welche Ausprägung letztendlich verwendet wird, hauptsache die Schnittstelle wird eingehalten.

Beispiel:

Angenommen Du schreibst eine Anwendung, die Versandinformationen aus Anwendungen verschiedener Logistik-Dienstleister (z.B. Deutsche Post, UPS, FedEx und GermanParcel) abrufen soll. Die Daten sind eigentlich immer die selben, nämlich Versandinformationen (also wie viele Pakete wurden mit welchem Gewicht zu welchem Porto an wen verschickt?). Du definierst also, wie die Versandinformationen aussehen sollen in einer Schnittstelle. Dann musst Du nur noch für jede Logistik-Programm eine Implementierung der Schnittstelle schreiben. Der Vorteil dabei: Wenn morgen auch noch der Logistik-Dienstleister TNT angebunden werden soll, kann das Deine Anwendung schon. Du musst nur noch die Schnittstelle auch für TNT implelementieren.

Ein weiterer grund für Schnittstellen ist Team-Entwicklung. An einer größeren Anwendung arbeiten mehrere Entwickler gleichzeitig. Ute ist für die GUI zuständig. Hugo für die Geschäftslogik und Gabi macht die Datenzugriffsfunktionen. Ute´s GUI muss natürlich auf die Geschäftslogik zugreifen. Die ist aber natürlich noch gar nicht fertig. Deshalb kommt Ernst, der Software-Architekt, und entwirft zusammen mit dem Team erstmal Schnittstellen für die drei Programmteile. Nun kann Ute einfach gegen die Schnittstelle programmieren. Hugo schreibt ihr schnell eine Dummy-Implementierung, damit sie testen kann. Wenn seine Geschäftslogik fertig ist, wird der Dummy durch die echte Implementierung ersetzt.

Vor dem schreiben von echtem Code sollte man deshalb zuerst Schnittstellen entwerfen. Vorteil: Beim Entwurf der Schnittstellen erkennt man bereits erste Ungereimtheiten im Konzept.

Man sollte bei Schnittstellen nicht immer nur an das "interface" Schlüsselwurt in C# denken. Eine WSDL-XML-Datei eines Webservices ist z.B. auch eine Schnittstelle, nur eben im XML-Format abgebildet.

Hoffentlich ist Dir das WARUM jetzt etwas klarer.

Original von AtzeX
Warum sollte man immer gegen Interfaces programmieren?

"Immer" ist auch ein wenig übertrieben. Da wo es Sinn macht.

Hallo Leute,

Beispiel:
Angenommen Du schreibst eine Anwendung, die Versandinformationen aus Anwendungen verschiedener Logistik-Dienstleister (z.B. Deutsche Post, UPS, FedEx und GermanParcel) abrufen soll. Die Daten sind eigentlich immer die selben, nämlich Versandinformationen (also wie viele Pakete wurden mit welchem Gewicht zu welchem Porto an wen verschickt?). Du definierst also, wie die Versandinformationen aussehen sollen in einer Schnittstelle. Dann musst Du nur noch für jede Logistik-Programm eine Implementierung der Schnittstelle schreiben. Der Vorteil dabei: Wenn morgen auch noch der Logistik-Dienstleister TNT angebunden werden soll, kann das Deine Anwendung schon. Du musst nur noch die Schnittstelle auch für TNT implelementieren.

Rainbird, kannst du für dies mal ein kleines Beispielprogramm schreiben, nur wie sowas aussehn könnte! (nur wenn du Zeit dafür hast)
Danke schon mal
Markus

Beispiel:

Zeit hab ich nicht, aber Mama Miraculi sagt "Ist doch schnell gemacht!"

So könnte eine solche Schnittstellenlösung aussehen:


    /// <summary>
    /// Beschreibt ein Warensendung.
    /// </summary>
    public class Shipment
    {
        // Öffentliche Felder
        private string ReferenceNo; // Referenznummer (z.B. Auftragsnummer)        
        private string CompanyName; // Firmenname
        private string FirstName; // Vorname
        private string LastName; // Nachname
        private string Department; // Abteilung
        private string Street; // Straße
        private string Zip; // Postleitzahl
        private string City; // Ort
        private string Country; // Land
        private string LogisticCompany; // Eindeutige Kennung des Logistik-Unternehmens (z.B.: "TNT")
        private string ProductName; // Produktname (z.B. "Express Paket Inland")
        private decimal Weight; // Gewicht
        private decimal InsuranceSum; // Transportversicherungssumme
        private string TrackingCode; // Paketvervolgungs-Code
        private decimal ShippingCharge; // Versandkosten
        private DateTime ShippingDate; // Versanddatum
        private string UserName; // Ausführender Benutzer
        private bool Canceled; // Storno-Schalter
    }

    /// <summary>
    /// Schnittstelle für Adaptermodule zu externen Versand-Applikationen.
    /// </summary>
    public interface IShipmentApplicationAdapter
    {
        /// <summary>
        /// Gibt alle heutigen Sendungen zurück.
        /// </summary>
        /// <returns>Liste mit Sendungen</returns>
        IList<Shipment> GetTodaysShipments();

        /// <summary>
        /// Gibt eine Sendung zu einem bestimmten Auftrag zurück oder
        /// Null, wenn der Auftrag noch nicht versendet wurde.
        /// </summary>
        /// <param name="referenceNo">Auftragsnummer o.ä.</param>
        /// <returns>Sendung</returns>
        Shipment GetShipmentByReferenceNo(string referenceNo);

        /// <summary>
        /// Sucht eine Sendung über den Paketverfolgungscode.
        /// </summary>
        /// <param name="trackingCode">Paketverfolgungscode</param>
        /// <returns>Sendung</returns>
        Shipment GetShipmentbyTrackingCode(string trackingCode);
    }

    /// <summary>
    /// Stellt Verbindung zu beliebigen Versand-Anwendungen über 
    /// Adapter-Komponenten her, deren Klassen IShipmentApplicationAdapter 
    /// implementieren.
    /// </summary>
    public class ShipmentApplicationAdapterFactory
    { 
        /// <summary>
        /// Erzeugt die passende Adapter-Komponente für die Applikation
        /// eines bestimmten Logistik-Unternehmens.
        /// </summary>
        /// <param name="logisticCompany">Eindeutige Kennung des Logistik-Unternehmens</param>
        /// <returns>Adapter-Komponente</returns>
        public static IShipmentApplicationAdapter CreateAdapter(string logisticCompany)
        {
            // Assembly- und Klassennamen der passenden Adapter-Komponente aus der Konfiguration abrufen
            string adapterAssemblyName=ConfigHelper.GetAdapterAssemblyName(logisticCompany);
            string adapterClassName=ConfigHelper.GetAdapterClassName(logisticCompany);

            // Assembly laden
            System.Reflection.Assembly adapterAssembly=System.Reflection.Assembly.Load(adapterAssemblyName);

            // Metadaten der Adapterklasse abrufen
            Type adapterClass=adapterAssembly.GetType(adapterClassName);

            // Komponente erzeugen, in Schnittstelle casten und zurückgeben
            return (IShipmentApplicationAdapter)Activator.CreateInstance(adapterClass);
        }
    }

Ahoi,

vielleicht auch ganz interessant im Bezug auf die Teamfähigkeit und parallele Entwicklung, 2 Webcasts von Ralf Westphal zum runterladen:

1. Contract First Design

2. Angewanntes Contract First Design (Microkernel)

Theoretisch ist das alles prima, in der Praxis aber nicht immer so leicht umzusetzen. Bei meinem derzeitigen Projekt habe ich es bisher größtenteils einhalten können. Da ich nur alleine dran arbeite, stellt man sich aber doch manchmal die Frage, ob der Aufwand sich generell IMMER lohnt. Jedenfalls schafft es eine gute einheitliche Struktur, die mir persönlich wiederum hilft. Ich für meine Teil werde es beibehalten, so gut es eben möglich ist.

Ein wichtiger Punkt ist noch, dass c# keine Mehrfachvererbung unterstützt. Dafür kann aber eine Klasse mehrere Interfaces implementieren.

In manchen Fällen solltest du dir aber auch überlegen, ob es nicht sinnvoll ist, eine abstrakte Klasse vorzuziehen, da du diese nachträglich einfacher erweitern kannst.

Falls der Text irgendwie verwirrend sein sollte, tut es mir leid, liegt dann wohl an meinem Schlafmangel, den ich nun mal nachholen werde...

ZzzZzz

Weiterentwicklung

Schnittstellen und dynamisches Laden von Programmteilen (Microkernel) erleichtern die Weiterentwicklung und den Ausbau einer Anwendung. Man kann so jedezeit komplette Programmteile ersetzen, ohne dass der Rest der Anwendung angefasst werden muss. Was hinter der Schnittstelle tatsächlich passiert, interessiert die anderen Programmteile ja nicht.

In der Praxis ist dies allerdings nur eine halbe Wahrheit, denn oft erfordern neue Features auch Änderungen an den Schnittstellen. Dann muss man natürlich genauso alles anfassen, wie bei der schlimmsten Spaghetti-Architektur.

Trotzdem sind der Team-Vorteil und die bessere Testbarkeit gute Gründe für eine solche Architektur. Auch betriebswirtschaftlich ist es interessant, da externe Lösungspartner so sehr einfach Erweiterungen schreiben können, ohne dass man ihnen den Quellcode geben müsste. Die Schnittstellen-Assembly und eine kleine Doku reichen völlig. Auch Vertriebsmodelle können von Komponentenorientierung profitieren, da Kunden nur das kaufen müssen (und auch nur das geliefert bekommen) was sie wirklich benötigen.

Von Pauschal-Aussagen halte ich aber nichts. Man kann nicht sagen, dass es IMMER so gemacht werden sollte.

An zu weit getriebener Vererbung habe ich mir schon die Finger verbrannt. Problematisch an Vererbung ist, dass alles untrennbar miteinander verbunden ist. Manchmal merkt man erst zu Spät, dass eine bestimmte Klasse besser ein ganz eigener Ast gewesen wäre.

Original von Rainbird
An zu weit getriebener Vererbung habe ich mir schon die Finger verbrannt. Problematisch an Vererbung ist, dass alles untrennbar miteinander verbunden ist. Manchmal merkt man erst zu Spät, dass eine bestimmte Klasse besser ein ganz eigener Ast gewesen wäre.

Da sprichst du einen viel wichtigeren Punkt an. Interfaces kann man auch noch nachträglich einziehen und so die Anwendung dynamisieren. Vererbungshierarchien wieder aufzubrechen und z.B. durch Komposition zu ersetzen, ist viel aufwändiger. Man sollte extrem vorsichtig sein Vererbung einzusetzen. Wenn eine Vererbunghierarchie auf einer fragilen Anforderung beruht, und diese sich später ändert, dann hat man den Salat.

Hallo Leute,
und danke Rainbird für dein Beispiel.
Ich möchte auch mal ein kleines Beispiel beschreiben, ob ich es verstanden habe.

Ich habe eine Datenklasse deren Objekte jeweils ein Datensatz aus einer Tabelle abbilden.
Und diese Datenklasse könnte doch von vier Interfaces "erben" und zwar 'ISpeicherbar', 'IVeraenderbar', 'ISuchbar' und 'ILoeschbar'.
Oder wie ist das zu sehen.
Bis dann
Markus

Ich möcht mich an dieser Stelle auch mal herzlichst für die Ausführungen zum Thema bedanken. Mir war der Begriff "Gegen Schnittstellen programmieren" bekannt und ich hätte es auch erklären können, aber durch euch hab ich es nun auch 100%ig verstanden.

Danke 😉

.:: SilvrGame - Browsergame Development with Silverlight
.:: Bionic's blOg

Hallo Reverent,

ja, ist ein sinnvolles Beispiel.

herbivore

Hallo,

sorry, dass ich mich nun erst melde, aber ich war leider verhindert.

Der Thread hat sich ja gut entwickelt.
Danke an alle Beteiligten!

Gruß,
AtzeX