... Da dies uns zwingt, Getter und Setter zu schaffen, die in der Praxis oft völlig fremd sind? Gibt es einen guten Grund für das Sprachdesign, warum Schnittstellen in den meisten (allen?) Sprachen es Mitgliedsfeldern nicht erlauben, die Schnittstellenspezifikation zu erfüllen?
In den meisten vorhandenen Sprachen werden Mitglieder grundlegend anders behandelt als Methoden. MUSS dies jedoch der Fall sein? In einer theoretischen Sprache konnten wir keine Schnittstelle haben, die (a) eine Null-Argument-Methode als Getter spezifizierte, aber ein lesbares Mitglied als Implementierer akzeptierte und (b) eine Einzel-Arg-Methode als Setter spezifizierte, aber akzeptierte ein beschreibbares Mitgliedsfeld als Implementierer in einer Sprache, die die Angabe der direkten Zugriffssteuerung für Variablen unterstützt, wenn diese deklariert werden? Eine prototypische Sprache, die eine Mehrfachvererbung ermöglicht (um den sehr gültigen Punkt anzusprechen, den einige Befragte angesprochen haben), wäre ideal für so etwas.
Bitte informieren Sie mich, wenn so etwas bereits existiert.
quelle
Antworten:
Nicht aus irgendeinem theoretisch begründeten Grund. Ich denke, die Gründe, warum Sie es nicht oft sehen, sind praktisch:
Das erste ist, wie Sie zur Laufzeit mit diesen Dingen umgehen. In Java oder C ++ ist eine Zuweisung an ein primitiv typisiertes öffentliches Mitglied wie
foo.x = 5
einfach zu handhaben: Dinge5
in ein Register eintragen, herausfinden, wo sich dasx
Mitglied im Speicher befindet,foo
und das Register dort ablegen. In einer Sprache wie C #, in der Klassen abfangende Zuweisungen an Mitglieder kapseln oder vorab berechnen können, wie diese Mitglieder bewerten , hat der Compiler kein a prioriWissen, ob die Implementierung einer zur Laufzeit zur Verfügung gestellten Klasse diese Funktion verwendet. Das bedeutet, dass alle Zuweisungen und Bewertungen von öffentlichen Mitgliedern von öffentlichen Mitgliedern auf die Implementierung verschoben werden müssen. * Vorgänge, bei denen viel Zugriff auf diese Mitglieder besteht, werden am Ende beeinträchtigt, da sie ständig Unterprogrammaufrufe ausführen, die mit regulären Eingriffen schneller erledigt werden könnten Inline-Zuweisungen. Wenn Evaluierung und Zuordnung von Gettern und Setzern getrennt bleiben, kann der Entwickler steuern, wie er in einer bestimmten Situation behandelt wird.Der zweite ist das Alter. Mit der möglichen Ausnahme von F # ist keine der heute sehr weit verbreiteten Sprachen jünger als ein Jahrzehnt, und selbst damals waren die zusätzlichen Zyklen, die erforderlich waren, um eine abgefangene Bewertung und Zuweisung zu ermöglichen, möglicherweise nicht akzeptabel.
Drittens ist Trägheit. Ich denke, bis zu einem gewissen Grad ist immer noch ein Stigma damit verbunden, öffentliche Mitglieder direkt zu beauftragen, selbst wenn die Sprache es der Klasse erlaubt, sie zu manipulieren, und die Leute immer noch denken, dass Getter und Setter besser sind. Das wird mit der Zeit nachlassen, genau wie bei allen Menschen, die ihre Nasen in der Luft hatten, gegenüber Menschen, die Programme in höheren Sprachen geschrieben haben, anstatt sich zu versammeln.
* Damit ist ein Kompromiss verbunden: Eine Sprache muss es schwer haben, Auswertungen / Zuweisungen an die Klasse zu verschieben oder zur Laufzeit zu blockieren, wenn Code, der Inline-Auswertungen / Zuweisungen für eine bestimmte Klasse verwendet, versucht, eine Verknüpfung mit einer Implementierung herzustellen, die sie abfängt . Persönlich halte ich letzteres für eine schlechte Idee, da es die Neukompilierung von Code erzwingt, der eine unveränderte API verwendet.
quelle
Ja tut es.
Mitgliedsfelder sind Daten pro Instanz. Daten müssen tatsächlich für jede Instanz irgendwo im Speicher vorhanden sein - es muss eine Adresse oder eine Folge von Adressen dafür reserviert sein. In Java, .NET und vielen anderen OO-Sprachen werden diese Daten auf dem Heap gespeichert.
Methoden sind Daten pro Klasse. Sie sind Zeiger auf eine vtable. So erhalten Methoden einen virtuellen Versand. Pro Klasse ist nur eine Instanz zugeordnet.
Eine Schnittstelle ist im Wesentlichen ein spezieller Klassentyp, der nur Methoden und keine Instanzdaten enthält. Das ist Teil der Definition der Schnittstelle. Wenn es Daten enthalten würde, wäre es keine Schnittstelle mehr, sondern eine abstrakte Klasse. Diese beiden Optionen sind natürlich in Ordnung, je nachdem, welche Art von Entwurfsmuster Sie implementieren möchten. Wenn Sie jedoch Schnittstellen Instanzdaten hinzufügen, entfernen Sie alles, was sie zu Schnittstellen macht .
OK, warum kann eine Schnittstellenmethode nicht auf ein Mitgliedsfeld verweisen? Weil es auf Klassenebene nichts gibt, worauf man hinweisen könnte. Auch hier ist eine Schnittstelle nichts anderes als eine Methodentabelle. Der Compiler kann nur eine Tabelle generieren, und jede darin enthaltene Methode muss auf dieselbe Speicheradresse verweisen. Daher ist es für Schnittstellenmethoden unmöglich, auf Klassenfelder zu verweisen, da das Klassenfeld für jede Instanz eine andere Adresse ist.
Vielleicht könnte der Compiler dies trotzdem akzeptieren und eine Getter-Methode generieren, auf die die Schnittstelle verweist - aber dann wird das im Wesentlichen eine Eigenschaft . Wenn Sie wissen möchten, warum Java keine Eigenschaften hat, dann ist das eine wirklich lange und langwierige Geschichte, auf die ich hier lieber nicht eingehen möchte. Aber wenn Sie daran interessiert sind, möchten Sie vielleicht einen Blick auf andere OO Sprachen haben , die tun Eigenschaften implementieren, wie C # oder Delphi. Die Syntax ist wirklich einfach:
Ja, das ist alles was dazu gehört. Wichtig: Dies sind keine Felder, die "int Bar" in der
Foo
Klasse ist eine Auto-Eigenschaft , und der Compiler macht genau das, was ich oben beschrieben habe: Automatische Generierung von Gettern und Setzern (sowie des Hintergrundelementfelds).Um die Antwort auf Ihre Frage zusammenzufassen:
quelle
Dies ist ein Ergebnis des Ausblendens von Informationen, dh diese Mitglieder sollten nicht als Felder sichtbar sein, sondern als Eigenschaften, die lokal gespeichert / zwischengespeichert werden können oder nicht
Ein Beispiel ist der Hash-Code, der sich ändert, wenn sich das Objekt ändert. Daher ist es besser, ihn zu berechnen, wenn er angefordert wird, und nicht jedes Mal, wenn sich das Objekt ändert
Außerdem sind die meisten Sprachen Einzelvererbung + Mehrfachimplementierungen. Wenn Sie zulassen, dass Felder Teil von Schnittstellen sind, gelangen Sie in das Gebiet der Mehrfachvererbung (es fehlt nur die Standardimplementierung von Funktionen), das ein Minenfeld von Randfällen ist, um das Richtige zu tun
quelle
Mitgliedsvariablen sind die Implementierung der Klasse, nicht die Schnittstelle. Möglicherweise möchten Sie die Implementierung ändern, sodass andere Klassen nicht direkt auf diese Implementierung verweisen dürfen.
Betrachten Sie eine Klasse mit den folgenden Methoden - C ++ - Syntax, aber das ist nicht wichtig ...
Es scheint ziemlich offensichtlich, dass es eine Mitgliedsvariable namens
mode
oderm_mode
ähnlich geben wird, die diesen Moduswert direkt speichert und so ziemlich das tut, was eine "Eigenschaft" in einigen Sprachen tun würde - aber das muss nicht unbedingt wahr sein. Es gibt viele verschiedene Möglichkeiten, wie damit umgegangen werden kann. Zum Beispiel könnte die Set_Mode-Methode ...Danach können viele andere Methoden der Beispielklasse über den Zeiger geeignete Methoden dieser internen Klasse aufrufen und so ein modusspezifisches Verhalten erhalten, ohne den aktuellen Modus überprüfen zu müssen.
Der Punkt hier ist weniger, dass es mehr als einen möglichen Weg gibt, um solche Dinge zu implementieren, und viel mehr, dass Sie irgendwann Ihre Meinung ändern können. Vielleicht haben Sie mit einer einfachen Variablen begonnen, aber alle switch-Anweisungen zur Identifizierung des Modus in jeder Methode sind schmerzhaft. Oder vielleicht haben Sie mit der Instanzzeiger-Sache angefangen, aber das stellte sich als zu schwer für Ihre Situation heraus.
Es stellt sich heraus, dass solche Dinge für alle Mitgliedsdaten passieren können. Möglicherweise müssen Sie die Einheiten, in denen ein Wert gespeichert ist, von Meilen auf Kilometer ändern, oder Sie stellen möglicherweise fest, dass Ihre Aufzählung zur eindeutigen Identifizierung des Falls nicht mehr alle Fälle eindeutig identifizieren kann, ohne zusätzliche Informationen oder was auch immer zu berücksichtigen.
Dies kann auch für Methoden passieren - einige Methoden sind ausschließlich für den internen Gebrauch bestimmt, hängen von der Implementierung ab und sollten privat sein. Viele Methoden sind jedoch Teil der Schnittstelle und müssen nicht umbenannt werden oder was auch immer, nur weil die interne Implementierung der Klasse ersetzt wurde.
Wenn Ihre Implementierung verfügbar gemacht wird, hängt unweigerlich anderer Code davon ab, und Sie sind daran gebunden, diese Implementierung beizubehalten. Wenn Ihre Implementierung vor anderem Code verborgen ist, kann diese Situation nicht auftreten.
Das Blockieren des Zugriffs auf Implementierungsdetails wird als "Ausblenden von Daten" bezeichnet.
quelle
Wie andere angemerkt haben, beschreibt eine Schnittstelle , was eine Klasse tut, nicht wie sie es tut. Es ist im Wesentlichen ein Vertrag, dem das Erben von Klassen entsprechen muss.
Was Sie beschreiben, ist etwas anders - eine Klasse, die eine Implementierung, aber möglicherweise nicht alle, als Form der Wiederverwendung bereitstellt . Diese werden allgemein als abstrakte Klassen bezeichnet und in einer Reihe von Sprachen (z. B. C ++ und Java) unterstützt. Diese Klassen können nicht eigenständig instanziiert werden, sondern nur von konkreten (oder weiteren abstrakten) Klassen geerbt werden.
Wie bereits erwähnt, sind abstrakte Klassen in Sprachen, die Mehrfachvererbung unterstützen, in der Regel von größtem Wert, da sie verwendet werden können, um einer größeren Klasse zusätzliche Funktionen bereitzustellen. Dies wird jedoch oft als schlechter Stil angesehen, da im Allgemeinen eine Aggregationsbeziehung ("has-a") angemessener wäre
quelle
Objective-C-Eigenschaften bieten diese Art von Funktionalität mit ihren synthetisierten Eigenschaftenzugriffern. Eine Objective-C-Eigenschaft ist im Wesentlichen nur ein Versprechen, dass eine Klasse Accessoren bereitstellt, typischerweise in der Form
foo
für den Getter undsetFoo:
für den Setter. Die Eigenschaftsdeklaration gibt bestimmte Attribute der Accessoren an, die sich auf die Speicherverwaltung und das Threading-Verhalten beziehen. Es gibt auch eine@synthesize
Direktive, die den Compiler anweist, Accessoren und sogar die entsprechende Instanzvariable zu generieren, sodass Sie sich nicht die Mühe machen müssen, Getter und Setter zu schreiben oder für jede Eigenschaft einen ivar zu deklarieren. Es gibt auch syntaktischen Zucker, der den Zugriff auf Eigenschaften wie den Zugriff auf die Felder einer Struktur aussehen lässt, aber tatsächlich dazu führt, dass die Zugriffsberechtigten aufgerufen werden.Das Ergebnis all dessen ist, dass ich eine Klasse mit Eigenschaften deklarieren kann:
und dann kann ich Code schreiben, der aussieht, als würde er direkt auf ivars zugreifen, aber tatsächlich Zugriffsmethoden verwenden:
Es wurde bereits gesagt, dass Accessoren es Ihnen ermöglichen, die Schnittstelle von der Implementierung zu trennen, und der Grund, warum Sie dies tun möchten, besteht darin, Ihre Fähigkeit zu bewahren, die Implementierung in Zukunft zu ändern, ohne den Clientcode zu beeinflussen. Der Nachteil ist, dass Sie diszipliniert genug sein müssen, um Accessoren zu schreiben und zu verwenden, um Ihre zukünftigen Optionen offen zu halten. Objective-C macht das Generieren und Verwenden von Accessoren so einfach wie das direkte Verwenden von Ivars. In der Tat ist die Verwendung von Eigenschaften viel einfacher als der direkte Zugriff auf ivars, da sie einen Großteil der Arbeit der Speicherverwaltung erledigen. Und wenn die Zeit gekommen ist, in der Sie Ihre Implementierung ändern möchten, können Sie jederzeit Ihre eigenen Accessoren bereitstellen, anstatt sie vom Compiler für Sie generieren zu lassen.
quelle
"Die meisten" Sprachen? Welche Sprachen haben Sie untersucht? In dynamischen Sprachen gibt es keine Schnittstellen. Es gibt nur drei oder vier statisch typisierte OO-Sprachen mit beliebiger Beliebtheit: Java, C #, Objective-C und C ++. C ++ hat keine Schnittstellen. Stattdessen verwenden wir abstrakte Klassen, und eine abstrakte Klasse kann ein öffentliches Datenelement enthalten. C # und Objective-C unterstützen beide Eigenschaften, sodass in diesen Sprachen keine Getter und Setter erforderlich sind. Das lässt nur Java. Vermutlich ist der einzige Grund, warum Java Eigenschaften nicht unterstützt, dass es schwierig war, sie abwärtskompatibel einzuführen.
quelle
In einigen Antworten werden "Ausblenden von Informationen" und "Implementierungsdetails" erwähnt, da Schnittstellenmethoden gegenüber Feldern bevorzugt werden. Ich denke, der Hauptgrund ist einfacher.
Schnittstellen sind eine Sprachfunktion, mit der Sie das Verhalten austauschen können, indem mehrere Klassen eine Schnittstelle implementieren. Das Verhalten einer Klasse wird durch ihre Methoden definiert. Felder hingegen spiegeln nur den Zustand eines Objekts wider .
Natürlich ist die Felder oder die Eigenschaften einer Klasse können Teil des Verhaltens sein, aber sie sind nicht der wichtigste Teil des durch die Schnittstelle definiert Vertrages; Sie sind nur ein Ergebnis. Aus diesem Grund können Sie beispielsweise mit C # Eigenschaften in einer Schnittstelle definieren, und in Java definieren Sie einen Getter in der Schnittstelle. Hier kommt ein bisschen verstecktes Informationsspiel ins Spiel, aber es ist nicht der Hauptpunkt.
Wie ich bereits sagte, können Sie mit Schnittstellen das Verhalten austauschen, aber schauen wir uns den Teil zum Austauschen an. Über eine Schnittstelle können Sie die zugrunde liegende Implementierung ändern, indem Sie eine neue Implementierungsklasse erstellen. Eine Schnittstelle, die aus Feldern besteht, macht also wenig Sinn, da Sie an der Implementierung von Feldern nicht viel ändern können.
Der einzige Grund, warum Sie eine Nur-Feld-Schnittstelle erstellen möchten, ist, wenn Sie sagen möchten, dass eine Klasse ein bestimmter Objekttyp ist. Ich habe das 'ist ein' Teil betont, weil Sie eher Vererbung als Schnittstellen verwenden sollten.
Randnotiz: Ich weiß, dass eine Komposition, die der Vererbung vorgezogen wird, normalerweise nur durch die Verwendung von Schnittstellen erreicht werden kann, aber die Verwendung von Schnittstellen, um eine Klasse mit einigen Eigenschaften zu "markieren", ist genauso schlecht. Die Vererbung ist bei sachgemäßer Verwendung großartig. Solange Sie sich an das Prinzip der Einzelverantwortung halten, sollten Sie keine Probleme haben.
Ich meine, ich habe zum Beispiel noch nie mit einer UI-Steuerungsbibliothek gearbeitet, die keine Vererbung verwendet. Der einzige Bereich, in dem Schnittstellen ins Spiel kommen, ist die Datenbereitstellung, z. B.
ITableDataAdapter
das Binden von Daten an ein Tabellensteuerelement. Dies ist genau die Art von Dingen, für die Schnittstellen gedacht sind, um austauschbares Verhalten zu haben .quelle
In Java ist der Grund, dass Schnittstellen nur die Angabe von Methoden erlauben .
Wenn Sie also etwas mit einem Feld im Schnittstellenvertrag möchten, müssen Sie dies als Getter- und Setter-Methode tun.
Siehe auch diese Frage: Wann sind Getter und Setter gerechtfertigt?
quelle
Der Sinn einer Schnittstelle besteht darin, Funktionen anzugeben, die Sie implementieren müssen, um zu behaupten, diese Schnittstelle zu unterstützen.
quelle
Mitglieder dürfen keine Schnittstellen verwenden, da diese Sprachen dann mit dem schrecklichen Übel, der Mehrfachvererbung, fertig werden müssten.
quelle