Warum kann ich keine geschützten Schnittstellenmitglieder haben?


75

Was ist das Argument gegen die Deklaration von Mitgliedern mit geschütztem Zugriff auf Schnittstellen? Dies ist beispielsweise ungültig:

public interface IOrange
{
    public OrangePeel Peel { get; }
    protected OrangePips Seeds { get; }
}

In diesem Beispiel IOrangewürde die Schnittstelle garantieren, dass Implementierer ihren Erben mindestens eine OrangePipsInstanz bereitstellen . Wenn der Implementierer wollte, konnte er den Umfang vollständig erweitern public:

public class NavelOrange : IOrange
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    protected OrangePips Seeds { get { return null; } }
}

public class ValenciaOrange : IOrange
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    public OrangePips Seeds { get { return new OrangePips(6); } }
}

Die Absicht der protectedMitglieder auf Schnittstellen besteht darin, einen Supportvertrag für Erben (Unterklassen) bereitzustellen , zum Beispiel:

public class SpecialNavelOrange : NavelOrange
{
    ...
    // Having a seed value is useful to me.
    OrangePips seeds = this.Seeds; 
    ...
}

(Zugegeben, das würde bei structs nicht funktionieren )

Ich kann nicht viel von einem Fall für privateoder internalModifikatoren in Schnittstellen sehen, aber die Unterstützung von beiden publicund protectedModifikatoren scheint vollkommen vernünftig.


Ich werde versuchen, die Nützlichkeit von protectedMitgliedern auf interfaces zu erklären, indem ich sie interfacevollständig von s trenne:

Stellen wir uns ein neues C # -Schlüsselwort vor, supportum Erbverträge durchzusetzen, sodass wir die Dinge wie folgt deklarieren:

public support IOrangeSupport
{
    OrangePips Seeds { get; }
}

Dies würde es uns ermöglichen, Klassen zu beauftragen, um ihren Erben geschützte Mitglieder zur Verfügung zu stellen:

public class NavelOrange : IOrange, IOrangeSupport
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    protected OrangePips Seeds { get { return null; } }
}

Dies ist nicht besonders nützlich, da Klassen diesen Vertrag bereits implizieren würden, indem sie die protectedMitglieder überhaupt erst zur Verfügung stellen.

Aber dann könnten wir auch folgendes tun:

public interface IOrange : IOrangeSupport
{
   ...
}

Dabei gilt dies IOrangeSupportfür alle Klassen IOrange, die bestimmte protectedMitglieder implementieren und von ihnen verlangen - was wir derzeit nicht tun können.



1
Betrachten Sie diesen Anwendungsfall, um diese Frage zu präzisieren. Ich habe eine abgeleitete Klasse, die von einer generischen Basisklasse erbt. Ich möchte ein geschütztes Mitglied hinzufügen, auf das über eine der generischen Varianten der abgeleiteten Klasse zugegriffen werden kann, das jedoch nicht der Außenwelt ausgesetzt ist. class Base<T> { } interface IDerived { string Secret { get; set; } } class Derived<T> : Base<T>, IDerived { protected string Secret; protected void LearnSecret(IDerived other) { var x = other.Secret; } }
Hypehuman

Ich bin ziemlich überrascht, dass in keiner der Antworten oder Kommentare das EIMI erwähnt wird. Das EIMI macht das Schnittstellenmitglied privat, wenn das Mitglied aus Sicht des Implementierungstyps betrachtet wird.
qqqqqqq

Antworten:


61

Ich denke, jeder hat den Punkt einer Schnittstelle getroffen, die nur öffentliche Mitglieder hat, keine Implementierungsdetails. Was Sie suchen, ist eine abstrakte Klasse .

public interface IOrange
{
    OrangePeel Peel { get; }
}

public abstract class OrangeBase : IOrange
{
    protected OrangeBase() {}
    protected abstract OrangePips Seeds { get; }
    public abstract OrangePeel Peel { get; }
}

public class NavelOrange : OrangeBase
{
    public override OrangePeel Peel { get { return new OrangePeel(); } }
    protected override OrangePips Seeds { get { return null; } }
}

public class ValenciaOrange : OrangeBase
{
    public override OrangePeel Peel { get { return new OrangePeel(); } }
    protected override OrangePips Seeds { get { return new OrangePips(6); } }
}

Bearbeiten: Es ist fair zu argumentieren, dass wenn wir eine PlasticOrange haben, die von einem Klassenornament abgeleitet ist, sie nur IOrange und nicht die Seeds-geschützte Methode implementieren kann. Das ist gut. Eine Schnittstelle ist per Definition ein Vertrag zwischen einem Aufrufer und einem Objekt, nicht zwischen einer Klasse und ihren Unterklassen. Die abstrakte Klasse ist so nah wie wir diesem Konzept kommen. Und das ist gut so. Was Sie im Wesentlichen vorschlagen, ist ein anderes Konstrukt in der Sprache, mit dem wir Unterklassen von einer Basisklasse zu einer anderen wechseln können, ohne den Build zu unterbrechen. Für mich macht das keinen Sinn.

Wenn Sie eine Unterklasse einer Klasse erstellen, ist die Unterklasse eine Spezialisierung der Basisklasse. Es sollte alle geschützten Mitglieder der Basisklasse vollständig kennen. Wenn Sie jedoch plötzlich die Basisklasse ausschalten möchten, macht es keinen Sinn, dass die Unterklasse mit jedem anderen IOrange funktioniert.

Ich nehme an, Sie haben eine faire Frage, aber es scheint ein Eckfall zu sein, und ich sehe ehrlich gesagt keinen Nutzen daraus.


10
Eine abstrakte Klasse funktioniert nur, wenn es sinnvoll ist, die Basisklassen von NavelOrangeund ValenciaOrangeauf dasselbe zu setzen. Angenommen, eine Klasse, PlasticOrangedie bereits von abgeleitet ist Ornament.
Ajlane

4
Dann würde ich bezweifeln, dass die Orange Samen hat oder schälen kann. Im Ernst, dies ist ein fairer Punkt, aber wir sind durch das OOP-Einzelvererbungsmodell eingeschränkt, sodass ich keine gute Antwort habe. Abgesehen davon löst diese Lösung den ursprünglichen Fall, es sei denn, Sie planen, das Modell zu erstellen, auf das Sie hinweisen. :)
Szymon Rozga

50

Ich kann nicht sehen, warum man das will. Wenn Sie möchten, dass abgeleitete Klassen eine Implementierung einer bestimmten Methode bereitstellen, wählen Sie abstrakte Basisklassen. Schnittstellen sind genau das - Schnittstellen. Ein öffentlicher Auftrag, sonst nichts. Stellen Sie sich die Schnittstelle als eine Spezifikation vor, die beschreibt, wie die Implementierung nach außen aussehen soll. Eine Spezifikation für einen zweipoligen Stecker gibt nicht an (zumindest nehme ich das an), wie seine interne Struktur aussehen sollte. Es muss nur mit einer Steckdose kompatibel sein. (Quelle: made-in-china.com )Stecker


13
Beste verdammte Erklärung, die ich je von einer Schnittstelle gehört habe. +1
WolfmanDragon

6
Ich denke, Sie werden feststellen, dass es ziemlich wichtig ist zu wissen, ob die Implementierung hinter diesem Stecker einen Strom von 110 V oder 220 V erfordert ;-)
mindplay.dk

5
@ mindplay.dk In einer perfekten Welt sollten Stecker für 110 und 220 unterschiedlich sein.
Anton Gogolev

6
@Anton in meiner perfekten Welt würden alle Geräte mit 110 und 220 Volt gleich gut funktionieren, und die Stecker wären alle gleich ;-)
mindplay.dk

1
Schnittstellen garantieren die Implementierung für externe Clients. Sie wissen mit Sicherheit, dass Klasse A die Methoden von Schnittstelle B implementiert hat, wenn Klasse A B implementiert. Sie können also A als B bedrohen und sicher sein, dass es als eine funktioniert. Abstrakte Klassen garantieren die Implementierung für interne Clients, Klassen, die davon abgeleitet sind, ermöglichen jedoch auch eine teilweise Implementierung. Es ist diese doppelte Verwendung, die problematisch ist. Ich denke, abstrakte Klassen hätten in zwei Konstrukte unterteilt werden sollen. Eine für die teilweise Implementierung, die keine Mehrfachvererbung zulässt, und eine für den abgeleiteten Vertrag, bei dem dies der Fall ist.
Didier A.

15

Weil es keinen Sinn macht. Eine Schnittstelle ist ein öffentlich zugänglicher Vertrag. Ich bin ein IThing, daher werde ich auf Anfrage IThing-Methoden durchführen. Sie können ein IThing nicht bitten, zu bestätigen, dass es Methoden ausführt, von denen es Ihnen nichts erzählen kann.


Aber ein IThing kann seinen Erben von Nicht- publicMethoden erzählen . Warum kann ich es nicht bitten, in diesem Zusammenhang zusätzliche IThing-Methoden durchzuführen?
Ajlane

Da dies keine IThing-Methoden sind, sind dies die Methoden / Eigenschaften der Basisklasse. Was ist, wenn zwei IThing-Implementierungen dieses geschützte Mitglied implementieren und eine dritte nicht, weil es einfach keinen Sinn ergibt? Was dann?
Szymon Rozga

@AJ - Verwirrt. Ein IThing kann seinen Erben nichts über Nichtöffentlichkeiten sagen, das ist der Punkt, oder?
Annakata

@siz: Der dritte muss es implementieren: Es ist in der Schnittstelle.
Ajlane

@annakata: Angenommen, AbstractThing implementiert IThing und ConcreteThing leitet sich von AbstractThing ab. AbstractThing kann ConcreteThing über geschützte (nicht öffentliche) Mitglieder informieren. Die Idee ist, dass IThing auch einige davon spezifizieren könnte
Ajlane

9

Es gibt Schnittstellen, über die Personen auf Ihre Klasse zugreifen können, ohne die konkrete Implementierung zu kennen. Die Implementierung wird vollständig vom Datenübertragungsvertrag getrennt.

Daher muss alles in einer Schnittstelle öffentlich sein. Nicht öffentliche Mitglieder sind nur dann nützlich, wenn Sie Zugriff auf die Implementierung haben und daher keinen sinnvollen Beitrag zu einer Schnittstellendefinition leisten.


7

Schnittstellenmitglieder sind eine öffentliche API. Dinge wie protectedusw. sind Implementierungsdetails - und Schnittstellen haben keine Implementierung. Ich vermute, was Sie suchen, ist eine explizite Schnittstellenimplementierung:

public class NavelOrange : IOrange
{
    public OrangePeel Peel { get { return new OrangePeel(); } }
    OrangePips IOrange.Seeds { get { return null; } }
}

Explizite Schnittstellenimplementierungen sind immer noch vorhanden public, daher ist es nicht wirklich das, wonach ich suche. Insbesondere denke ich, dass protectedMitglieder eine API zwischen Gruppen von Basisklassen und ihren Erben bereitstellen würden.
Ajlane

Das ist streng zwischen der Basisklasse und dem Erben - es hat einfach nichts mit der Schnittstelle zu tun. Wenn Sie eine geschützte Methode in der Basisklasse möchten, deklarieren Sie einfach eine.
Marc Gravell

Richtig, aber nehmen wir an, wir haben eine breite Palette von Basisklassen, die die Schnittstelle implementieren. Es erscheint vernünftig, einen Vertrag zu definieren, der besagt, dass jeder, der aus diesen Klassen stammt, einen bestimmten Unterstützungsstandard erwarten kann.
Ajlane

2

Eine Schnittstelle ist wie die Form eines Schlüssels.

Geben Sie hier die Bildbeschreibung ein

Es ist nicht der Schlüssel.

Es ist nicht das Schloss.

Es ist nur der schlanke Kontaktpunkt.

Aus diesem Grund müssen alle Mitglieder der Schnittstelle (die die Form des Schlüssels definiert) öffentlich sein.

Damit ein Schlüssel ein Schloss öffnen kann, ist es wichtig, dass beide dieselbe Form haben.

Indem Sie die Form (die Benutzeroberfläche) öffentlich machen , können Sie andere kompatible Schlösser oder kompatible Schlüssel erstellen lassen.

Wenn Sie es (die Schnittstelle) intern machen , können andere Benutzer keine kompatiblen Schlösser oder kompatiblen Schlüssel erstellen.


1

Eine Schnittstelle ist ein Vertrag, der Kunden bestimmte Funktionen verspricht. Mit anderen Worten, der Zweck einer Schnittstelle besteht darin, einen Typ in sie umwandeln und auf diese Weise an Code weitergeben zu können, der die von dieser Schnittstelle garantierten Funktionen benötigt. Da Client-Code eines Typs nicht auf geschützte Mitglieder dieses Typs zugreifen kann, ist es nicht sinnvoll, geschützte Elemente in einer Schnittstelle zu deklarieren.


1

Im Gegensatz zu abstrakten Basisklassen würden geschützte Schnittstellen eine "Mehrfachvererbung" (von abstrakten Klassen) ermöglichen, die ich ein- oder zweimal als nützlich empfunden hätte ...


0

Bei einer Schnittstelle geht es darum, was ein bestimmtes Objekt tun kann. Wenn Sie also eine Klasse verwenden, die diese Schnittstelle implementiert, erwartet der Entwickler, dass alle Mitglieder implementiert werden, sodass der Modifikator für den geschützten Zugriff für Schnittstellen keine Bedeutung hat.


1
Einverstanden, aber ich frage nicht so sehr, warum die Implementierung von Mitgliedern einer Schnittstelle nicht optional sein kann (wie sie es nicht sollten), sondern warum ich ihre Zugriffsebene nicht angeben kann.
Ajlane

0

Das derzeitige Design von Schnittstellen ist fundiert und bietet Implementierern mehr Flexibilität. Denken Sie daran, dass Schnittstellen sehr oft von Framework-Programmierern geschrieben werden und Implementierer unterschiedliche Leute sind. Die Durchsetzung durchzusetzen wäre unnötig hart.


0

Durch die Implementierung einer Schnittstelle gibt der Typ an, dass er einen bestimmten Satz von Methoden unterstützt. Wenn eine dieser Methoden nicht öffentlich wäre, wäre sie für Anrufer nicht verfügbar, und daher würde der Typ die angegebene Schnittstelle nicht unterstützen.


0

Eine Schnittstelle enthält nur öffentliche Mitglieder. Geschützt bedeutet, dass alles, was Sie deklarieren, nur für die Klasse und abgeleitete Klasseninstanzen verfügbar ist.


"Methoden" sollten "Mitglieder" lauten - können auch Eigenschaften und Ereignisse sein
Marc Gravell

0

Jede Klasse, die eine .net-Schnittstelle implementiert, muss Implementierungen aller Schnittstellenmitglieder enthalten. Darüber hinaus kann jede Klasse einer abgeleiteten Klasse beliebige Mitglieder aussetzen. Das Erfordernis, dass eine Implementierung einer Schnittstelle ein Mitglied enthalten muss, das nur von abgeleiteten Klassen verwendet werden kann, würde keinen nützlichen Zweck erfüllen, es sei denn, entweder (1) ein solches Mitglied könnte für etwas außerhalb der Schnittstelle sichtbar sein oder (2) Schnittstellenimplementierungen könnten verwendet werden Mitglieder, die sie selbst nicht definiert haben. Wenn Schnittstellen verschachtelte Klassen enthalten dürfen (die auf die protectedMitglieder der Schnittstellen zugreifen können ), dannprotectedSchnittstellenmitglieder wären sinnvoll. In der Tat könnten sie sehr nützlich sein, wenn eine in einer Schnittstelle verschachtelte Klasse Erweiterungsmethoden für diese Schnittstelle definieren könnte. Leider gibt es keine solche Einrichtung.

Übrigens wäre es auch ohne die Möglichkeit, Klassen in Schnittstellen zu verschachteln, nützlich, einen internalZugriffsmodifikator auf Schnittstellenmitglieder anzuwenden , so dass nur die Assembly, in der die Schnittstelle definiert ist, Implementierungen dafür definieren kann.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.