Die Kapselung sagt mir, dass ich alle oder fast alle Felder privat machen und diese durch Getter / Setter verfügbar machen soll. Aber jetzt erscheinen Bibliotheken wie Lombok , die es uns ermöglichen, alle privaten Felder durch eine kurze Annotation freizugeben @Data
. Es werden Getter, Setter und Setting-Konstruktoren für alle privaten Bereiche erstellt.
Könnte mir jemand erklären, was es heißt, alle Bereiche als privat zu verstecken und sie anschließend durch eine zusätzliche Technologie freizulegen? Warum verwenden wir dann einfach nicht nur öffentliche Felder? Ich habe das Gefühl, dass wir einen langen und schweren Weg zurückgelegt haben, nur um zum Ausgangspunkt zurückzukehren.
Ja, es gibt andere Technologien, die mit Gettern und Setzern arbeiten. Und wir können sie nicht durch einfache öffentliche Felder verwenden. Aber diese Technologien erschienen nur , weil wir hatten diese zahlreichen Eigenschaften - private Felder hinter öffentlichen Getter / Setter. Wenn wir nicht die Eigenschaften hätten, würden diese Technologien einen anderen Weg entwickeln und öffentliche Bereiche unterstützen. Und alles wäre einfach und wir hätten jetzt keinen Bedarf an Lombok.
Was ist der Sinn des Ganzen in diesem Zyklus? Und hat die Kapselung in der realen Programmierung jetzt wirklich einen Sinn?
quelle
"It will create getters, setters and setting constructors for all private fields."
- Die Art und Weise Sie dieses Tool beschreiben, klingt es wie es ist Verkapselung aufrechterhalten wird . (Zumindest im Sinne eines losen, automatisierten, etwas anämischen Modells.) Also, was genau ist das Problem?Antworten:
Wenn Sie alle Ihre Attribute mit Gettern / Setzern belichten, erhalten Sie nur eine Datenstruktur, die noch in C oder einer anderen prozeduralen Sprache verwendet wird. Es ist keine Verkapselung, und Lombok vereinfacht die Arbeit mit prozeduralem Code. Getter / Setter so schlecht wie normale öffentliche Felder. Es gibt wirklich keinen Unterschied.
Und Datenstruktur ist kein Objekt. Wenn Sie damit beginnen, ein Objekt aus dem Schreiben einer Schnittstelle zu erstellen, werden Sie der Schnittstelle niemals Getter / Setter hinzufügen. Das Offenlegen Ihrer Attribute führt zu Spaghetti-Prozedurcode, bei dem sich die Manipulation mit Daten außerhalb eines Objekts befindet und sich über die gesamte Codebasis verteilt. Jetzt beschäftigen Sie sich mit Daten und Manipulationen mit Daten, anstatt mit Objekten zu sprechen. Mit Gettern / Setzern verfügen Sie über eine datengesteuerte prozedurale Programmierung, bei der die Manipulation auf direkte und zwingende Weise erfolgt. Daten abrufen - etwas tun - Daten setzen.
Bei der OOP-Kapselung handelt es sich um einen Elefanten, wenn dies auf die richtige Weise erfolgt. Sie sollten Status- und Implementierungsdetails kapseln, damit das Objekt die volle Kontrolle darüber hat. Die Logik wird im Objekt fokussiert und nicht über die gesamte Codebasis verteilt. Und ja - die Kapselung ist bei der Programmierung immer noch unerlässlich, da der Code besser gewartet werden kann.
EDITS
Nachdem ich die Diskussionen gesehen habe, möchte ich einige Dinge hinzufügen:
Wenn wir also zu der Frage zurückkehren, warum wir das tun sollten. Betrachten Sie dieses einfache Beispiel:
Hier haben wir ein Dokument, das interne Details durch Getter verfügbar macht, und einen externen Prozedurcode in
printDocument
Funktion, der mit dem außerhalb des Objekts arbeitet. Warum ist das so schlimm? Denn jetzt haben Sie nur C-Style-Code. Ja, es ist strukturiert, aber was ist wirklich der Unterschied? Sie können C-Funktionen in verschiedenen Dateien und mit Namen strukturieren. Und genau das tun diese sogenannten Schichten. Die Serviceklasse besteht nur aus einer Reihe von Prozeduren, die mit Daten arbeiten. Dieser Code ist weniger wartbar und hat viele Nachteile.Vergleichen Sie mit diesem. Jetzt haben wir einen Vertrag und die Details zur Implementierung dieses Vertrags sind im Objekt verborgen . Jetzt können Sie diese Klasse wirklich testen und diese Klasse kapselt einige Daten. Wie es mit diesen Daten funktioniert, ist ein Gegenstand. Um jetzt mit dem Objekt zu sprechen , müssen Sie ihn bitten , sich selbst zu drucken. Das ist Verkapselung und das ist ein Objekt. Mit OOP erhalten Sie die volle Leistungsfähigkeit der Abhängigkeitsinjektion, des Verspottens, des Testens, der Einzelverantwortung und vieler Vorteile.
quelle
Der Sinn ist, dass Sie das nicht tun sollen .
Kapselung bedeutet, dass Sie nur die Felder verfügbar machen, auf die andere Klassen zugreifen müssen, und dass Sie sehr selektiv und vorsichtig damit sind.
Geben Sie nicht standardmäßig alle Felder Getter und Setter an!
Das widerspricht vollkommen dem Geist der JavaBeans-Spezifikation, aus der ironischerweise das Konzept der öffentlichen Getter und Setter stammt.
Wenn Sie sich jedoch die Spezifikation ansehen, werden Sie feststellen, dass die Erstellung von Gettern und Setzern sehr selektiv sein sollte und dass es sich um schreibgeschützte Eigenschaften (kein Setter) und schreibgeschützte Eigenschaften (kein Setter) handelt getter).
Ein weiterer Faktor ist, dass Getter und Setter nicht unbedingt einen einfachen Zugang zum privaten Bereich darstellen . Ein Getter kann den Wert, den er zurückgibt, auf eine beliebig komplexe Weise berechnen, möglicherweise im Cache speichern. Ein Setter kann den Wert validieren oder Listener benachrichtigen.
Sie sind also hier: Kapselung bedeutet, dass Sie nur die Funktionen verfügbar machen, die Sie tatsächlich benötigen. Aber wenn Sie nicht darüber nachdenken, was Sie herausstellen müssen, und einfach alles aufdecken, indem Sie einer syntaktischen Transformation folgen, dann ist das natürlich keine wirkliche Verkapselung.
quelle
getFieldName()
es für uns ein automatischer Vertrag wird. Wir werden dahinter kein komplexes Verhalten erwarten. In den meisten Fällen wird die Verkapselung gerade abgebrochen.Ich denke, der Kern der Sache erklärt sich aus Ihrem Kommentar:
Das Problem, das Sie haben, ist, dass Sie das Persistenzdatenmodell mit dem aktiven Datenmodell mischen.
Eine Anwendung verfügt im Allgemeinen über mehrere Datenmodelle:
über dem Datenmodell, das es tatsächlich verwendet, um seine Berechnungen durchzuführen.
Im Allgemeinen sollten die für die Kommunikation mit der Außenseite verwendeten Datenmodelle isoliert und unabhängig von dem inneren Datenmodell (Business Object Model, BOM) sein, für das Berechnungen durchgeführt werden:
In diesem Szenario ist es völlig in Ordnung, wenn für die in den Kommunikationsebenen verwendeten Objekte alle Elemente öffentlich sind oder von Gettern / Setzern verfügbar gemacht werden. Das sind einfache Objekte ohne jegliche Invariante.
Auf der anderen Seite sollte Ihre Stückliste Invarianten enthalten, was im Allgemeinen viele Setter ausschließt (Getter haben keinen Einfluss auf Invarianten, obwohl sie die Verkapselung bis zu einem gewissen Grad reduzieren).
quelle
Folgendes berücksichtigen..
Sie haben eine
User
Klasse mit einer Eigenschaftint age
:Sie möchten dies so skalieren, dass a
User
ein Geburtsdatum hat und nicht nur ein Alter. Getter verwenden:Wir können das
int age
Feld gegen ein komplexeres austauschenLocalDate dateOfBirth
:Keine Vertragsverstöße, kein Codefehler. Nur die interne Darstellung muss vergrößert werden, um sich auf komplexere Verhaltensweisen vorzubereiten.
Das Feld selbst ist eingekapselt.
Nun, um die Bedenken auszuräumen ..
Die
@Data
Annotation von Lombok ähnelt den Datenklassen von Kotlin .Nicht alle Klassen repräsentieren Verhaltensobjekte. Die Unterbrechung der Kapselung hängt von Ihrer Verwendung der Funktion ab. Sie sollten nicht alle Ihre Felder über Getter aussetzen .
In einem allgemeineren Sinne ist die Kapselung das Verbergen von Informationen. Wenn Sie missbrauchen
@Data
, ist es leicht anzunehmen, dass Sie wahrscheinlich die Kapselung brechen. Aber das heißt nicht, dass es keinen Zweck hat. JavaBeans zum Beispiel werden von manchen missbilligt. Es wird jedoch in großem Umfang in der Unternehmensentwicklung eingesetzt.Würden Sie zu dem Schluss kommen, dass die Unternehmensentwicklung aufgrund der Verwendung von Bohnen schlecht ist? Natürlich nicht! Die Anforderungen unterscheiden sich von denen der Standardentwicklung. Können Bohnen missbraucht werden? Na sicher! Sie werden die ganze Zeit missbraucht!
Lombok unterstützt auch
@Getter
und@Setter
unabhängig - verwenden Sie, was Ihre Anforderungen erfordern.quelle
@Data
Anmerkung auf eine Art, die durch Design unencapsulates alle FeldersetAge(xyz)
So wird Kapselung in der objektorientierten Programmierung nicht definiert. Einkapselung bedeutet, dass jedes Objekt wie eine Kapsel sein sollte, deren äußere Hülle (die öffentliche API) den Zugang zu seinem Inneren (den privaten Methoden und Feldern) schützt und reguliert und es vor dem Blick verbirgt. Durch das Ausblenden von Interna können sich Anrufer nicht auf Interna verlassen, sodass Interna geändert werden können, ohne dass die Anrufer geändert (oder sogar neu kompiliert) werden. Durch die Kapselung kann jedes Objekt seine eigenen Invarianten erzwingen, indem nur Aufrufern sichere Operationen zur Verfügung gestellt werden.
Die Kapselung ist daher ein Sonderfall des Versteckens von Informationen, bei dem jedes Objekt seine Interna verbirgt und seine Invarianten erzwingt.
Das Generieren von Get- und Setter-Werten für alle Felder ist eine ziemlich schwache Form der Kapselung, da die Struktur der internen Daten nicht verborgen ist und Invarianten nicht erzwungen werden können. Dies hat den Vorteil, dass Sie die Art und Weise ändern können, in der die Daten intern gespeichert werden (solange Sie in die alte Struktur konvertieren können), ohne jedoch die Aufrufer ändern (oder sogar neu kompilieren zu müssen).
Dies ist zum Teil auf einen historischen Unfall zurückzuführen. Auffällig ist, dass in Java Methodenaufrufausdrücke und Feldzugriffsausdrücke an der Aufrufstelle syntaktisch unterschiedlich sind, dh das Ersetzen eines Feldzugriffs durch einen Getter- oder Setter-Aufruf unterbricht die API einer Klasse. Wenn Sie daher möglicherweise einen Accessor benötigen, müssen Sie jetzt einen schreiben oder die API unterbrechen. Dieses Fehlen der Unterstützung von Eigenschaften auf Sprachebene steht in scharfem Kontrast zu anderen modernen Sprachen, insbesondere C # und EcmaScript .
Strike 2 ist, dass die JavaBeans- Spezifikation Eigenschaften als Getter / Setter definiert hat, Felder waren keine Eigenschaften. Infolgedessen unterstützten die meisten frühen Enterprise-Frameworks Getter / Setter, jedoch keine Felder. Das ist längst Vergangenheit ( Java Persistence API (JPA) , Bean Validation , Java-Architektur für XML-Bindung (JAXB) , Jackson)Inzwischen sind alle Support-Bereiche in Ordnung), aber die alten Tutorials und Bücher verweilen weiter und nicht jeder weiß, dass sich die Dinge geändert haben. In einigen Fällen kann das Fehlen der Unterstützung von Eigenschaften auf Sprachebene immer noch problematisch sein (z. B. weil das verzögerte Laden einzelner Entitäten durch JPA nicht ausgelöst wird, wenn öffentliche Felder gelesen werden), aber die meisten öffentlichen Felder funktionieren einwandfrei. Mein Unternehmen schreibt also alle DTOs für ihre REST-APIs mit öffentlichen Feldern (es wird schließlich nicht mehr öffentlich, als über das Internet übertragen wird :-).
Das heißt, Lomboks
@Data
erzeugt mehr als nur Getter / Setter: Sie erzeugttoString()
auchhashCode()
undequals(Object)
, was sehr wertvoll sein kann.Die Verkapselung kann von unschätzbarem Wert oder völlig unbrauchbar sein, sie hängt vom zu verkapselnden Objekt ab. Je komplexer die Logik innerhalb der Klasse ist, desto größer ist im Allgemeinen der Vorteil der Kapselung.
Automatisch generierte Get- und Setter-Funktionen für jedes Feld werden im Allgemeinen zu häufig verwendet. Sie können jedoch hilfreich sein, um mit älteren Frameworks zu arbeiten oder um die gelegentliche Framework-Funktion zu verwenden, die für Felder nicht unterstützt wird.
Die Kapselung kann mit Gettern und Befehlsmethoden erreicht werden. Setter sind normalerweise nicht geeignet, da erwartet wird, dass sie nur ein einziges Feld ändern, während bei der Pflege von Invarianten möglicherweise mehrere Felder gleichzeitig geändert werden müssen.
Zusammenfassung
Getter / Setter bieten eine eher schlechte Kapselung.
Die Prävalenz von Gettern / Setzern in Java beruht auf dem Mangel an Sprachniveauunterstützung für Eigenschaften und fragwürdigen Entwurfswahlen in seinem historischen Komponentenmodell, die in vielen Lehrmaterialien und den von ihnen unterrichteten Programmierern verankert sind.
Andere objektorientierte Sprachen wie EcmaScript unterstützen Eigenschaften auf Sprachebene, sodass Getter eingeführt werden können, ohne die API zu beschädigen. In solchen Sprachen können Getter eingeführt werden, wenn Sie sie tatsächlich benötigen, und nicht im Voraus, nur für den Fall, dass Sie sie einmal brauchen. Dies sorgt für ein weitaus angenehmeres Programmiererlebnis.
quelle
Ich habe mir diese Frage tatsächlich gestellt.
Es ist jedoch nicht ganz wahr. Die Prävalenz von Get- / Setter-IMO wird durch die Java-Bean-Spezifikation verursacht, die dies erfordert. Es ist also so ziemlich ein Merkmal der nicht objektorientierten Programmierung, sondern der bean-orientierten Programmierung, wenn Sie so wollen. Der Unterschied zwischen den beiden besteht in der Abstraktionsschicht, in der sie existieren. Beans sind eher eine Systemschnittstelle, dh auf einer höheren Ebene. Sie abstrahieren von der OO-Basisarbeit oder sind zumindest dazu gedacht - wie immer werden die Dinge zu oft genug zu weit getrieben.
Ich würde sagen, es ist etwas bedauerlich, dass diese Bean-Sache, die in der Java-Programmierung allgegenwärtig ist, nicht mit einer entsprechenden Java-Sprachfunktion einhergeht - ich denke an so etwas wie das Properties-Konzept in C #. Für diejenigen, die sich dessen nicht bewusst sind, ist es ein Sprachkonstrukt, das so aussieht:
Wie auch immer, die eigentliche Implementierung profitiert immer noch sehr stark von der Kapselung.
quelle
Die einfache Antwort lautet hier: Sie haben absolut Recht. Getter und Setter eliminieren den größten Teil (aber nicht den gesamten) Wert der Verkapselung. Dies soll nicht heißen, dass Sie jedes Mal, wenn Sie eine get- und / oder set-Methode haben, die die Kapselung unterbricht, aber wenn Sie allen privaten Mitgliedern Ihrer Klasse blind Zugriffsmethoden hinzufügen, machen Sie es falsch.
Getter sind Setter, die in der Java-Programmierung allgegenwärtig sind, da das JavaBean-Konzept als Methode zur dynamischen Bindung der Funktionalität an vorgefertigten Code eingeführt wurde. Sie könnten zum Beispiel ein Formular in einem Applet haben (jemand erinnert sich daran?), Das Ihr Objekt inspiziert, alle Eigenschaften findet und die Felder als anzeigt. Dann kann die Benutzeroberfläche diese Eigenschaften basierend auf Benutzereingaben ändern. Sie als Entwickler kümmern sich dann nur darum, die Klasse zu schreiben und dort eine Validierung oder Geschäftslogik usw. abzulegen.
Beispielbohnen verwenden
Das ist an sich keine schreckliche Idee, aber ich war noch nie ein großer Fan des Ansatzes in Java. Es geht nur gegen den Strich. Verwenden Sie Python, Groovy usw. Etwas, das diesen Ansatz natürlicher unterstützt.
Das JavaBean-Ding ist außer Kontrolle geraten, weil es JOBOL erstellt hat, dh von Java geschriebene Entwickler, die OO nicht verstehen. Grundsätzlich wurden Objekte zu nichts weiter als zu Datenbeuteln, und die gesamte Logik wurde mit langen Methoden nach außen geschrieben. Weil es als normal angesehen wurde, galten Leute wie du und ich, die dies in Frage stellten, als verrückt. In letzter Zeit habe ich eine Veränderung erlebt und dies ist keine so starke Außenseiterposition
Die XML-Bindung ist eine harte Nuss. Dies ist wahrscheinlich kein gutes Schlachtfeld, um sich gegen JavaBeans zu behaupten. Wenn Sie diese JavaBeans erstellen müssen, versuchen Sie, sie aus dem tatsächlichen Code herauszuhalten. Behandeln Sie sie als Teil der Serialisierungsebene.
quelle
[Serializable]
Attribut und seine Verwandten. Warum 101 spezifische Serialisierungsmethoden / -klassen schreiben, wenn Sie nur eine schreiben können, indem Sie Reflection einsetzen?Wie viel können wir ohne Getter erreichen? Ist es möglich, sie vollständig zu entfernen? Welche Probleme entstehen dadurch? Könnten wir das
return
Keyword sogar verbieten ?Es stellt sich heraus, dass Sie viel tun können, wenn Sie bereit sind, die Arbeit zu erledigen. Wie können dann Informationen aus diesem vollständig gekapselten Objekt herauskommen? Durch einen Mitarbeiter.
Anstatt sich von Code Fragen stellen zu lassen, sagen Sie, was zu tun ist. Wenn diese Dinge auch keine Dinge zurückgeben, müssen Sie nichts dagegen tun, was sie zurückgeben. Wenn Sie also überlegen,
return
stattdessen nach einem Output Port Collaborator zu greifen, der alles tut, was noch zu tun ist.Das hat Vorteile und Konsequenzen. Sie müssen über mehr nachdenken, als nur über das, was Sie zurückgegeben hätten. Sie müssen darüber nachdenken, wie Sie das als Nachricht an ein Objekt senden, das nicht danach gefragt hat. Es kann sein, dass Sie dasselbe Objekt herausgeben, das Sie zurückgegeben hätten, oder es reicht aus, einfach eine Methode aufzurufen. Das Denken ist mit Kosten verbunden.
Der Vorteil ist, dass Sie jetzt mit Blick auf die Benutzeroberfläche sprechen. Dies bedeutet, dass Sie den vollen Nutzen aus der Abstraktion ziehen können.
Es gibt Ihnen auch einen polymorphen Versand, denn während Sie wissen, was Sie sagen, müssen Sie nicht genau wissen, zu was Sie es sagen.
Sie denken vielleicht, dies bedeutet, dass Sie nur einen Weg durch einen Stapel von Schichten gehen müssen, aber es stellt sich heraus, dass Sie Polymorphismus verwenden können, um rückwärts zu gehen, ohne verrückte zyklische Abhängigkeiten zu erzeugen.
Es könnte so aussehen:
Wenn Sie so codieren können, ist die Verwendung von Gettern eine Wahl. Kein notwendiges Übel.
quelle
Dies ist ein kontroverses Thema (wie Sie sehen können), da eine Reihe von Dogmen und Missverständnissen mit den angemessenen Bedenken in Bezug auf das Thema Getter und Setter verwechselt werden. Aber kurz gesagt, es ist nichts falsch daran
@Data
und es bricht nicht die Kapselung.Warum Getter und Setter anstelle von öffentlichen Feldern verwenden?
Weil Getter / Setter für die Kapselung sorgen. Wenn Sie einen Wert als öffentliches Feld verfügbar machen und später ändern, um den Wert im laufenden Betrieb zu berechnen, müssen Sie alle Clients ändern, die auf das Feld zugreifen. Das ist eindeutig schlecht. Es ist ein Implementierungsdetail, ob eine Eigenschaft eines Objekts in einem Feld gespeichert, im laufenden Betrieb generiert oder von einem anderen Ort abgerufen wird, sodass der Unterschied nicht für Clients sichtbar gemacht werden sollte. Getter / Setter Setter lösen dies, da sie die Implementierung verbergen.
Aber wenn der Getter / Setter nur ein zugrunde liegendes privates Feld widerspiegelt, ist es nicht genauso schlimm?
Nein! Der Punkt ist, dass die Kapselung es Ihnen ermöglicht , die Implementierung zu ändern, ohne die Clients zu beeinflussen. Ein Feld ist möglicherweise immer noch eine gute Methode zum Speichern von Werten, solange die Kunden es nicht wissen oder sich darum kümmern müssen.
Aber werden Getter / Setter nicht automatisch aus Feldern generiert, die die Kapselung durchbrechen?
Nein die Kapselung ist noch da!
@Data
Anmerkungen sind nur eine bequeme Möglichkeit, Getter / Setter-Paare zu schreiben, die ein zugrunde liegendes Feld verwenden. Für den Standpunkt eines Clients ist dies genau wie ein reguläres Get- / Setter-Paar. Wenn Sie sich entscheiden, die Implementierung neu zu schreiben, können Sie dies dennoch tun, ohne den Client zu beeinträchtigen. So erhalten Sie das Beste aus beiden Welten: Kapselung und kurze Syntax.Aber manche sagen, Getter / Setter sind immer schlecht!
Es gibt eine separate Kontroverse, bei der einige der Ansicht sind, dass das Get- / Setter-Muster unabhängig von der zugrunde liegenden Implementierung immer schlecht ist . Die Idee ist , dass Sie nicht Werte von Objekten erhalten setzen oder sollten vielmehr jede Interaktion zwischen Objekten als Nachrichten modelliert werden soll , wenn ein Objekt fragt ein anderes Objekt , etwas zu tun. Dies ist meist ein Stück Dogma aus den Anfängen des objektorientierten Denkens. Es wird jetzt davon ausgegangen, dass für bestimmte Muster (z. B. Wertobjekte, Datenübertragungsobjekte) Getter / Setter durchaus geeignet sein können.
quelle
Kapselung hat einen Zweck, kann aber auch missbraucht oder missbraucht werden.
Betrachten Sie etwas wie die Android-API, die Klassen mit Dutzenden (wenn nicht Hunderten) von Feldern enthält. Das Anzeigen dieser Felder durch den Benutzer der API erschwert das Navigieren und Verwenden und gibt dem Benutzer die falsche Vorstellung, dass er mit den Feldern, die möglicherweise in Konflikt mit der beabsichtigten Verwendung stehen, alles Mögliche tun kann. Daher ist die Kapselung in diesem Sinne großartig, um Wartbarkeit, Benutzerfreundlichkeit und Lesbarkeit zu gewährleisten und verrückte Fehler zu vermeiden.
Andererseits können auch POD- oder einfache alte Datentypen wie eine Struktur aus C / C ++, in der alle Felder öffentlich sind, nützlich sein. Es ist nur eine Möglichkeit, das "Encapsulation-Muster" beizubehalten, wenn unbrauchbare Get- / Setter vorhanden sind, wie sie durch die @ data-Annotation in Lombok generiert wurden. Einer der wenigen Gründe, warum wir in Java "nutzlose" Getter / Setter machen, ist, dass die Methoden einen Vertrag liefern .
In Java können Sie keine Felder in einer Schnittstelle haben. Daher verwenden Sie Getter und Setter, um eine gemeinsame Eigenschaft anzugeben, über die alle Implementierer dieser Schnittstelle verfügen. In neueren Sprachen wie Kotlin oder C # sehen wir das Konzept von Eigenschaften als Felder, für die Sie Setter und Getter deklarieren können. Am Ende sind nutzlose Getter / Setter eher ein Erbe, mit dem Java leben muss, es sei denn, Oracle fügt ihm Eigenschaften hinzu. Kotlin zum Beispiel, eine andere von JetBrains entwickelte JVM-Sprache, verfügt über Datenklassen, die im Grunde das tun, was die @ data-Annotation in Lombok tut.
Auch hier einige Beispiele:
Dies ist ein schlechter Fall von Verkapselung. Der Getter und Setter sind effektiv nutzlos. Die Kapselung wird meistens verwendet, da dies der Standard in Sprachen wie Java ist. Hilft eigentlich nicht, außer die Konsistenz über die Codebasis aufrechtzuerhalten.
Dies ist ein gutes Beispiel für die Verkapselung. Die Kapselung wird verwendet, um einen Vertrag durchzusetzen, in diesem Fall IDataInterface. Der Zweck der Kapselung in diesem Beispiel besteht darin, den Konsumenten dieser Klasse zu veranlassen, die von der Schnittstelle bereitgestellten Methoden zu verwenden. Obwohl Getter und Setter nichts Besonderes tun, haben wir jetzt ein gemeinsames Merkmal zwischen DataClass und anderen Implementierern von IDataInterface definiert. Somit kann ich eine Methode wie diese haben:
Nun, wenn ich über Kapselung spreche, denke ich, dass es wichtig ist, auch das Syntaxproblem anzugehen. Ich sehe oft Leute, die sich über die Syntax beschweren, die erforderlich ist, um die Kapselung zu erzwingen, anstatt die Kapselung selbst. Ein Beispiel, das mir einfällt, ist Casey Muratori (Sie können seine Schimpfe hier sehen ).
Angenommen, Sie haben eine Spielerklasse, die gekapselt ist und deren Position um 1 Einheit verschoben werden soll. Der Code würde so aussehen:
Ohne Kapselung würde es so aussehen:
Hier argumentiert er, dass Verkapselungen zu viel mehr Typisierung ohne zusätzlichen Nutzen führen und dies in vielen Fällen wahr sein kann, aber etwas beachten. Das Argument ist gegen die Syntax und nicht gegen die Kapselung. Sogar in Sprachen wie C, denen das Konzept der Kapselung fehlt, werden Sie häufig Variablen in Strukturen sehen, die mit '_' oder 'my' oder was auch immer versehen sind, um anzuzeigen, dass sie vom Verbraucher der API nicht verwendet werden sollten, als ob sie es wären Privatgelände.
Die Tatsache, dass es sich um eine Kapselung handelt, kann dazu beitragen, dass Code viel einfacher zu warten und zu verwenden ist. Betrachten Sie diese Klasse:
Wenn die Variablen in diesem Beispiel öffentlich wären, wäre ein Verbraucher dieser API verwirrt, wann er posX und posY und wann setPosition () verwenden soll. Durch das Ausblenden dieser Details helfen Sie dem Verbraucher, Ihre API auf intuitive Weise besser zu nutzen.
Die Syntax ist jedoch in vielen Sprachen eine Einschränkung. Neuere Sprachen bieten jedoch Eigenschaften, die uns die nette Syntax von Veröffentlichungsmitgliedern und die Vorteile der Kapselung bieten. Sie finden Eigenschaften in C #, Kotlin, auch in C ++, wenn Sie MSVC verwenden. Hier ist ein Beispiel in Kotlin.
Klasse VerticalList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {field = y; ...}}
Hier haben wir dasselbe wie im Java-Beispiel erreicht, aber wir können posX und posY so verwenden, als wären sie öffentliche Variablen. Wenn ich aber versuche, ihren Wert zu ändern, wird der Body des Settlers set () ausgeführt.
In Kotlin wäre dies beispielsweise das Äquivalent einer Java Bean mit Gettern, Setzern, Hashcode, Equals und ToString:
Beachten Sie, wie diese Syntax es uns ermöglicht, eine Java-Bean in einer Zeile auszuführen. Sie haben das Problem, das eine Sprache wie Java beim Implementieren der Kapselung hat, richtig bemerkt, aber das ist der Fehler von Java, nicht der Kapselung selbst.
Sie sagten, dass Sie Lomboks @Data verwenden, um Getter und Setter zu generieren. Beachten Sie den Namen @Data. Es ist hauptsächlich für Datenklassen gedacht, die nur Daten speichern und serialisiert und deserialisiert werden sollen. Stellen Sie sich so etwas wie eine Sicherungsdatei aus einem Spiel vor. In anderen Szenarien, wie bei einem UI-Element, möchten Sie jedoch unbedingt Setter, da das Ändern des Werts einer Variablen möglicherweise nicht ausreicht, um das erwartete Verhalten zu erzielen.
quelle
Die Kapselung gibt Ihnen Flexibilität . Durch die Trennung von Struktur und Schnittstelle können Sie die Struktur ändern, ohne die Schnittstelle zu ändern.
Wenn Sie beispielsweise feststellen, dass Sie eine Eigenschaft basierend auf anderen Feldern berechnen müssen, anstatt das zugrunde liegende Feld bei der Konstruktion zu initialisieren, können Sie einfach den Getter ändern. Wenn Sie das Feld direkt verfügbar gemacht hätten, müssten Sie stattdessen die Benutzeroberfläche ändern und an jeder Verwendungsstelle Änderungen vornehmen.
quelle
Ich werde versuchen, den Problemraum der Kapselung und des Klassenentwurfs zu veranschaulichen und Ihre Frage am Ende zu beantworten.
Wie in anderen Antworten erwähnt, besteht der Zweck der Kapselung darin, interne Details eines Objekts hinter einer öffentlichen API zu verbergen, die als Vertrag dient. Es ist sicher, dass das Objekt seine Interna ändert, da es weiß, dass es nur über die öffentliche API aufgerufen wird.
Ob es sinnvoll ist, öffentliche Felder oder Get- / Setter, übergeordnete Transaktionsmethoden oder die Weitergabe von Nachrichten zu verwenden, hängt von der Art der zu modellierenden Domäne ab. In dem Buch Akka Concurrency (das ich empfehlen kann, auch wenn es etwas veraltet ist) finden Sie ein Beispiel, das dies veranschaulicht, das ich hier abkürzen werde.
Betrachten Sie eine Benutzerklasse:
Dies funktioniert problemlos in einem Single-Thread-Kontext. Die Domain, die modelliert wird, ist der Name einer Person, und die Mechanik, wie dieser Name gespeichert wird, kann von den Setzern perfekt eingekapselt werden.
Stellen Sie sich jedoch vor, dies muss in einem Kontext mit mehreren Threads bereitgestellt werden. Angenommen, ein Thread liest regelmäßig den Namen:
Und zwei andere Threads kämpfen gegen ein Tauziehen und setzen es abwechselnd gegen Hillary Clinton und Donald Trump durch. Sie müssen jeweils zwei Methoden aufrufen. Meistens funktioniert das gut, aber hin und wieder sehen Sie einen Hillary Trump oder Donald Clinton vorbeiziehen.
Sie können dieses Problem nicht durch Hinzufügen einer Sperre innerhalb der Setter lösen, da die Sperre nur für die Dauer des Einstellens des Vor- oder Nachnamens gehalten wird. Die einzige Lösung durch Sperren ist das Hinzufügen einer Sperre um das gesamte Objekt, wodurch jedoch die Kapselung unterbrochen wird, da der aufrufende Code die Sperre verwalten muss (und Deadlocks verursachen kann).
Wie sich herausstellt, gibt es keine saubere Lösung durch Verriegelung. Die saubere Lösung besteht darin, die Einbauten erneut einzukapseln, indem sie gröber gemacht werden:
Der Name selbst ist unveränderlich geworden, und Sie sehen, dass seine Mitglieder öffentlich sein können, da es sich jetzt um ein reines Datenobjekt handelt, das nach seiner Erstellung nicht mehr geändert werden kann. Im Gegenzug wurde die öffentliche API der Benutzerklasse mit nur einem Setter gröber, sodass der Name nur noch als Ganzes geändert werden kann. Es kapselt mehr seines internen Zustands hinter der API.
Was Sie in diesem Zyklus sehen, sind Versuche, Lösungen für eine bestimmte Reihe von Umständen zu breit anzuwenden. Ein geeigneter Kapselungsgrad setzt voraus, dass Sie die zu modellierende Domäne verstehen und den richtigen Kapselungsgrad anwenden. Manchmal bedeutet dies, dass alle Felder öffentlich sind, manchmal (wie in Akka-Anwendungen), dass Sie bis auf eine einzige Methode zum Empfangen von Nachrichten keine öffentliche API haben. Das Konzept der Kapselung selbst, dh das Verbergen von Interna hinter einer stabilen API, ist jedoch der Schlüssel zur Programmierung von Software in großem Maßstab, insbesondere in Systemen mit mehreren Threads.
quelle
Ich kann mir einen Anwendungsfall vorstellen, bei dem dies sinnvoll ist. Möglicherweise haben Sie eine Klasse, auf die Sie ursprünglich über eine einfache Get- / Setter-API zugreifen. Sie erweitern oder ändern später, sodass nicht mehr dieselben Felder verwendet werden, sondern dasselbe API unterstützt wird .
Ein etwas ausgeklügeltes Beispiel: Ein Punkt, der als kartesisches Paar mit
p.x()
und beginntp.y()
. Sie erstellen später eine neue Implementierung oder Unterklasse, die Polarkoordinaten verwendet, sodass Sie auchp.r()
und aufrufen könnenp.theta()
, aber Ihren Clientcode, der aufruftp.x()
undp.y()
gültig bleibt. Die Klasse selbst konvertiert transparent von der internen Polarform, das heißt,y()
würde jetztreturn r * sin(theta);
. (In diesem Beispiel ist die Einstellung nurx()
odery()
nicht so sinnvoll, aber dennoch möglich.)In diesem Fall könnte es sein, dass Sie sagen: "Ich bin froh, dass ich mich darum gekümmert habe, automatisch Getter und Setter zu deklarieren, anstatt die Felder zu veröffentlichen, oder ich hätte dort meine API auflösen müssen."
quelle
Es hat absolut keinen Sinn. Die Tatsache, dass Sie diese Frage stellen, zeigt jedoch, dass Sie nicht verstanden haben, was Lombok tut, und dass Sie nicht verstehen, wie OO-Code mit Kapselung geschrieben wird. Lassen Sie uns ein bisschen zurückspulen ...
Einige Daten für eine Klasseninstanz sind immer intern und sollten niemals verfügbar gemacht werden. Einige Daten für eine Klasseninstanz müssen extern festgelegt werden, und einige Daten müssen möglicherweise aus einer Klasseninstanz zurückgegeben werden. Wir möchten vielleicht ändern, wie die Klasse unter der Oberfläche arbeitet, also verwenden wir Funktionen, um Daten abzurufen und festzulegen.
Einige Programme möchten den Status für Klasseninstanzen speichern, daher verfügen diese möglicherweise über eine Serialisierungsschnittstelle. Wir fügen weitere Funktionen hinzu, mit denen die Klasseninstanz ihren Status im Speicher ablegen und ihren Status aus dem Speicher abrufen kann. Dadurch bleibt die Kapselung erhalten, da die Klasseninstanz immer noch die Kontrolle über ihre eigenen Daten hat. Möglicherweise serialisieren wir private Daten, aber der Rest des Programms hat keinen Zugriff darauf (oder genauer gesagt, wir pflegen eine chinesische Mauer, indem wir uns dafür entscheiden, diese privaten Daten nicht absichtlich zu beschädigen), und die Klasseninstanz kann (und sollte) Führen Sie Integritätsprüfungen bei der Deserialisierung durch, um sicherzustellen, dass die Daten wieder in Ordnung sind.
Manchmal müssen die Daten auf Bereich, Integrität oder ähnliches überprüft werden. Wenn wir diese Funktionen selbst schreiben, können wir das alles tun. In diesem Fall wollen oder brauchen wir Lombok nicht, weil wir das alles selbst machen.
Häufig stellen Sie jedoch fest, dass ein extern eingestellter Parameter in einer einzelnen Variablen gespeichert ist. In diesem Fall benötigen Sie vier Funktionen, um den Inhalt dieser Variablen abzurufen / festzulegen / zu serialisieren / zu deserialisieren. Wenn Sie diese vier Funktionen jedes Mal selbst schreiben, werden Sie langsamer und sind fehleranfällig. Die Automatisierung des Prozesses mit Lombok beschleunigt Ihre Entwicklung und beseitigt die Möglichkeit von Fehlern.
Ja, es wäre möglich, diese Variable öffentlich zu machen. In dieser speziellen Codeversion wäre sie funktional identisch. Aber gehen Sie zurück zu dem Grund, warum wir Funktionen verwenden: "Wir möchten vielleicht ändern, wie die Klasse unter der Oberfläche arbeitet ..." Wenn Sie Ihre Variable öffentlich machen, beschränken Sie Ihren Code jetzt und für immer darauf, diese öffentliche Variable als zu haben die Schnittstelle. Wenn Sie jedoch Funktionen verwenden oder Lombok verwenden, um diese Funktionen automatisch für Sie zu generieren, können Sie die zugrunde liegenden Daten und die zugrunde liegende Implementierung jederzeit in der Zukunft ändern.
Macht das alles klarer?
quelle
Ich bin eigentlich kein Java-Entwickler. aber das folgende ist ziemlich plattformunabhängig.
Fast alles, was wir schreiben, verwendet öffentliche Getter und Setter, die auf private Variablen zugreifen. Die meisten Getter und Setter sind trivial. Aber wenn wir entscheiden, dass der Setter etwas neu berechnen muss oder der Setter eine Validierung durchführt oder die Eigenschaft an eine Eigenschaft einer Mitgliedsvariablen dieser Klasse weiterleiten muss, ist dies für den gesamten Code völlig unterbrechungsfrei und daher binärkompatibel kann das eine Modul austauschen.
Wenn wir entscheiden, dass diese Eigenschaft wirklich im Handumdrehen berechnet werden soll, muss sich der gesamte Code, der sie anzeigt, nicht ändern, und nur der Code, der darauf schreibt, muss sich ändern, und die IDE kann ihn für uns finden. Wenn wir uns dazu entschließen, dass es sich um ein beschreibbares berechnetes Feld handelt (das nur ein paar Mal durchgeführt werden musste), können wir das auch. Das Schöne ist, dass einige dieser Änderungen binärkompatibel sind (Änderungen am schreibgeschützten berechneten Feld sind zwar nicht theoretisch, können aber in der Praxis vorkommen).
Wir haben am Ende viele, viele Kleinigkeiten mit komplizierten Setzern. Wir hatten auch einige Caching-Getter. Das Endergebnis ist, dass Sie davon ausgehen dürfen, dass Getter einigermaßen billig sind, Setter jedoch möglicherweise nicht. Andererseits sind wir klug genug zu entscheiden, dass Setter nicht auf der Festplatte bleiben.
Aber ich musste den Typen ausfindig machen, der blindlings alle Mitgliedsvariablen in Eigenschaften änderte. Er wusste nicht, was atomare Addition ist, also änderte er das, was wirklich eine öffentliche Variable sein musste, in eine Eigenschaft und brach den Code auf subtile Weise.
quelle
Getter und Setter sind eine "Nur-für-Fall" -Maßnahme, die hinzugefügt wurde, um künftiges Refactoring zu vermeiden, wenn sich die interne Struktur oder die Zugriffsanforderungen während des Entwicklungsprozesses ändern.
Angenommen, einige Monate nach der Freigabe informiert Sie Ihr Client, dass eines der Felder einer Klasse manchmal auf negative Werte gesetzt ist, obwohl es in diesem Fall höchstens auf 0 geklemmt werden sollte. Wenn Sie öffentliche Felder verwenden, müssen Sie in Ihrer gesamten Codebasis nach jeder Zuordnung dieses Felds suchen, um die Klemmfunktion auf den Wert anzuwenden, den Sie festlegen möchten. Beachten Sie, dass Sie dies immer tun müssen, wenn Sie dieses Feld ändern. aber das nervt. Wenn Sie stattdessen bereits Getter und Setter verwendet hätten, hätten Sie einfach Ihre setField () -Methode ändern können, um sicherzustellen, dass diese Klemmung immer angewendet wird.
Das Problem mit "antiquierten" Sprachen wie Java ist, dass sie die Verwendung von Methoden für diesen Zweck anregen, was Ihren Code nur unendlich ausführlicher macht. Es ist schmerzhaft zu schreiben und schwer zu lesen, weshalb wir IDE verwenden, die dieses Problem auf die eine oder andere Weise mildert. Die meisten IDEs generieren automatisch Getter und Setter für Sie und verbergen sie, sofern nicht anders angegeben. Lombok geht noch einen Schritt weiter und generiert sie einfach prozedural während der Kompilierung, um Ihren Code besonders glatt zu halten. Andere modernere Sprachen haben dieses Problem jedoch auf die eine oder andere Weise einfach gelöst. Scala- oder .NET-Sprachen, zum Beispiel
In VB .NET oder C # könnten Sie beispielsweise einfach alle Felder so einrichten, dass sie nur öffentliche Felder enthalten, und sie später als privat kennzeichnen, ihren Namen ändern und eine Eigenschaft mit dem vorherigen Namen von verfügbar machen das Feld, in dem Sie das Zugriffsverhalten des Felds bei Bedarf anpassen können. Wenn Sie in Lombok das Verhalten eines Get- oder Setters optimieren müssen, können Sie diese Tags bei Bedarf einfach entfernen und Ihre eigenen mit den neuen Anforderungen codieren, da Sie sicher sind, dass Sie in anderen Dateien keine Änderungen vornehmen müssen.
Grundsätzlich sollte die Art und Weise, wie Ihre Methode auf ein Feld zugreift, transparent und einheitlich sein. In modernen Sprachen können Sie "Methoden" mit der gleichen Zugriffs- / Aufrufsyntax eines Felds definieren, sodass diese Änderungen bei Bedarf vorgenommen werden können, ohne sich während der frühen Entwicklung Gedanken zu machen habe diese Funktion nicht. Alles, was Lombok tut, ist, Ihnen Zeit zu sparen, da Sie mit der von Ihnen verwendeten Sprache keine Zeit für eine "nur für den Fall" -Methode sparen möchten.
quelle
foo.bar
jedoch möglicherweise von einem Methodenaufruf verarbeitet. Sie behaupten, dies sei der "veralteten" Art überlegen, eine API wie Methodenaufrufe aussehen zu lassenfoo.getBar()
. Wir scheinen zuzustimmen, dass öffentliche Felder problematisch sind, aber ich behaupte, dass die "antiquierte" Alternative der "modernen" überlegen ist, da unsere gesamte API symmetrisch ist (es sind alles Methodenaufrufe). Im "modernen" Ansatz müssen wir entscheiden, welche Dinge Eigenschaften und welche Methoden sein sollen, was alles überkompliziert (besonders wenn wir Reflexion verwenden!).Ja, das ist widersprüchlich. Ich bin zuerst auf Eigenschaften in Visual Basic gestoßen. Bis dahin gab es meiner Erfahrung nach in anderen Sprachen keine Eigenschaftsumhüllungen um Felder. Nur öffentliche, private und geschützte Felder.
Eigenschaften waren eine Art Einkapselung. Ich habe unter Visual Basic-Eigenschaften eine Möglichkeit verstanden, die Ausgabe eines oder mehrerer Felder zu steuern und zu bearbeiten, während das explizite Feld und sogar sein Datentyp ausgeblendet werden, beispielsweise das Ausgeben eines Datums als Zeichenfolge in einem bestimmten Format. Aber auch dann ist man nicht "Versteckspiel und Enthüllungsspiel" aus der Perspektive eines größeren Objekts.
Eigenschaften rechtfertigen sich jedoch durch getrennte Eigenschafts-Getter und -Setter. Ein Feld ohne eine Eigenschaft auszustellen war alles oder nichts - wenn Sie es lesen könnten, könnten Sie es ändern. So kann man jetzt schwache Klassen mit geschützten Setzern rechtfertigen.
Also, warum haben wir / sie nicht nur tatsächliche Methoden angewendet? Weil es Visual Basic (und VBScript ) (oooh! Aaah!) War, das für die Massen (!) Kodierte, und es war der letzte Schrei. Und so dominierte schließlich eine Idiotokratie.
quelle