Ich habe es nicht leicht mit dem Entwerfen von Kursen. Ich habe gelesen, dass Objekte ihr Verhalten und nicht ihre Daten enthüllen. Anstatt Getter / Setter zum Ändern von Daten zu verwenden, sollten die Methoden einer bestimmten Klasse daher "Verben" oder Aktionen sein, die auf das Objekt angewendet werden. In einem 'Account'-Objekt hätten wir zum Beispiel die Methoden Withdraw()
und Deposit()
nicht setAmount()
usw. Siehe: Warum Getter- und Setter-Methoden böse sind .
Wie kann man zum Beispiel bei einer Kundenklasse, die viele Informationen über den Kunden enthält, z. B. Name, DOB, Tel., Adresse usw. vermeiden, dass Getter / Setter all diese Attribute abrufen und festlegen? Welche Art von 'Verhalten' kann man schreiben, um all diese Daten zu füllen?
quelle
name()
aufCustomer
ist so klar, oder klarer, als eine Methode aufgerufengetName()
.Antworten:
Wie in ganz wenige Antworten und Kommentaren angegeben, DTOs ist angemessen und nützlich in einigen Situationen, vor allem in Daten über Grenzen hinweg zu übertragen (zB auf JSON Serialisierung über einen Webdienst zu senden). Für den Rest dieser Antwort werde ich das mehr oder weniger ignorieren und über Domänenklassen sprechen und wie sie entworfen werden können, um Getter und Setter zu minimieren (wenn nicht zu eliminieren) und dennoch in einem großen Projekt nützlich zu sein. Ich werde auch nicht darüber sprechen, warum Getter oder Setter entfernt werden oder wann dies zu tun ist, da dies eigene Fragen sind.
Stellen Sie sich beispielsweise vor, Ihr Projekt ist ein Brettspiel wie Schach oder Schlachtschiff. Es gibt verschiedene Möglichkeiten, dies in einer Präsentationsebene darzustellen (Konsolenanwendung, Webdienst, GUI usw.), Sie haben jedoch auch eine Kerndomäne. Eine Klasse, die Sie haben könnten, ist die
Coordinate
Vertretung einer Position auf dem Brett. Die "böse" Art zu schreiben wäre:(Ich werde der Kürze halber Codebeispiele in C # schreiben und nicht in Java, weil ich damit besser vertraut bin. Hoffentlich ist das kein Problem. Die Konzepte sind dieselben und die Übersetzung sollte einfach sein.)
Setter entfernen: Unveränderlichkeit
Während öffentliche Getter und Setter potenziell problematisch sind, sind Setter die weitaus "schlimmeren" der beiden. Sie sind in der Regel auch leichter zu beseitigen. Der Vorgang ist einfach: Setzen Sie den Wert im Konstruktor. Alle Methoden, die das Objekt zuvor mutiert haben, sollten stattdessen ein neues Ergebnis zurückgeben. Damit:
Beachten Sie, dass dies nicht gegen andere Methoden in der Klasse schützt, die X und Y mutieren. Um strenger unveränderlich zu sein, können Sie
readonly
(final
in Java) verwenden. Egal, ob Sie Ihre Eigenschaften wirklich unveränderlich machen oder nur die direkte öffentliche Veränderung durch Setter verhindern - es ist der Trick, Ihre öffentlichen Setter zu entfernen. In den allermeisten Situationen funktioniert dies einwandfrei.Getter entfernen, Teil 1: Entwerfen für Verhalten
Das Obige ist alles gut und gut für Setter, aber in Bezug auf Getters haben wir uns tatsächlich selbst in den Fuß geschossen, bevor wir überhaupt angefangen haben. Unser Prozess bestand darin, zu überlegen, was eine Koordinate ist - die Daten, die sie darstellt - und eine Klasse darum herum zu erstellen. Stattdessen hätten wir mit dem Verhalten beginnen sollen, das wir von einer Koordinate benötigen. Übrigens wird dieser Prozess durch TDD unterstützt, bei dem wir solche Klassen erst extrahieren, wenn wir sie benötigen. Wir beginnen also mit dem gewünschten Verhalten und arbeiten von dort aus.
Nehmen wir also an, der erste Ort, an dem Sie einen benötigen,
Coordinate
war die Kollisionserkennung: Sie wollten überprüfen, ob zwei Teile den gleichen Platz auf dem Brett einnehmen. Hier ist der "böse" Weg (Konstruktoren der Kürze halber weggelassen):Und hier ist der gute Weg:
(
IEquatable
Implementierung der Einfachheit halber abgekürzt). Wir haben es geschafft, unsere Getter zu entfernen, indem wir Verhaltensdaten anstatt Daten zu modellieren.Beachten Sie, dass dies auch für Ihr Beispiel relevant ist. Möglicherweise verwenden Sie ein ORM oder zeigen Kundeninformationen auf einer Website oder Ähnlichem an. In diesem Fall ist eine Art
Customer
DTO wahrscheinlich sinnvoll. Nur weil Ihr System Kunden enthält und diese im Datenmodell dargestellt werden, bedeutet dies nicht automatisch, dass Sie eineCustomer
Klasse in Ihrer Domäne haben sollten. Vielleicht taucht beim Entwerfen eines Verhaltens eines auf, aber wenn Sie Getter vermeiden möchten, sollten Sie keines präventiv erstellen.Getter entfernen, Teil 2: Äußeres Verhalten
So ist die oben ein guter Anfang, aber früher oder später werden Sie wahrscheinlich in einer Situation führen , wo Sie Verhalten haben , die mit einer Klasse zugeordnet ist, die in irgendeiner Weise auf die staatliche Klasse abhängt, die aber nicht gehört zu der Klasse. Diese Art von Verhalten ist typisch für die Service-Schicht Ihrer Anwendung.
Wenn Sie sich unser
Coordinate
Beispiel ansehen, möchten Sie Ihr Spiel eventuell dem Benutzer vorstellen. Dies kann bedeuten, dass Sie auf dem Bildschirm zeichnen. Sie könnten beispielsweise ein UI-Projekt haben, mitVector2
dem ein Punkt auf dem Bildschirm dargestellt wird. Es wäre jedoch unangemessen, wenn dieCoordinate
Klasse die Konvertierung von einer Koordinate zu einem Punkt auf dem Bildschirm übernehmen würde - das würde alle möglichen Probleme mit der Präsentation in Ihre Kerndomäne bringen. Leider ist diese Art von Situation in OO-Design inhärent.Die erste Option , die sehr häufig gewählt wird, besteht darin, die verdammten Getter bloßzustellen und damit zur Hölle zu sagen. Dies hat den Vorteil der Einfachheit. Aber da wir über das Vermeiden von Gettern sprechen, lasst uns aus Gründen der Argumentation sagen, wir lehnen dieses ab und sehen, welche anderen Optionen es gibt.
Eine zweite Möglichkeit besteht darin,
.ToDTO()
Ihrer Klasse eine Methode hinzuzufügen . Dies - oder ähnliches - kann ohnehin erforderlich sein, wenn Sie beispielsweise das Spiel speichern möchten, das Sie benötigen, um praktisch Ihren gesamten Status zu erfassen. Der Unterschied zwischen der Ausführung für Ihre Dienste und dem direkten Zugriff auf den Getter ist jedoch mehr oder weniger ästhetisch. Es hat immer noch genauso viel "Böses" an sich.Eine dritte Option - die ich in einigen Pluralsight-Videos von Zoran Horvat befürwortet habe - ist die Verwendung einer modifizierten Version des Besuchermusters. Dies ist eine ziemlich ungewöhnliche Verwendung und Variation des Musters, und ich denke, die Laufleistung der Leute wird massiv davon abhängen, ob es die Komplexität erhöht, ohne einen wirklichen Gewinn zu erzielen, oder ob es ein netter Kompromiss für die Situation ist. Die Idee ist im Wesentlichen, das Standard-Besuchermuster zu verwenden, aber die
Visit
Methoden verwenden den Status, den sie als Parameter benötigen, anstelle der Klasse, die sie besuchen. Beispiele finden Sie hier .Für unser Problem wäre eine Lösung unter Verwendung dieses Musters:
Wie Sie wahrscheinlich sehen können
_x
und_y
nicht mehr wirklich gekapselt sind. Wir könnten sie extrahieren, indem wir eine erstellen,IPositionTransformer<Tuple<int,int>>
die sie direkt zurückgibt. Je nach Geschmack haben Sie möglicherweise das Gefühl, dass dies die gesamte Übung sinnlos macht.Mit öffentlichen Gettern ist es jedoch sehr einfach, Dinge falsch zu machen, indem Daten direkt ausgelesen und unter Verstoß gegen Tell, Don't Ask verwendet werden . Während es mit diesem Muster einfacher ist , es richtig zu machen: Wenn Sie Verhalten erstellen möchten, erstellen Sie automatisch einen damit verknüpften Typ. Verstöße gegen TDA werden sehr offensichtlich riechen und erfordern wahrscheinlich eine einfachere und bessere Lösung. In der Praxis machen es diese Punkte viel einfacher, es richtig zu machen, OO, als die "böse" Art und Weise, die Getter ermutigen.
Schließlich , auch wenn es zunächst nicht offensichtlich ist, gibt es in der Tat Möglichkeiten, um aussetzen genug von dem, was Sie brauchen , wie Verhalten , um zu vermeiden Zustand zu belichten. Wenn Sie beispielsweise unsere vorherige Version verwenden,
Coordinate
deren einziges öffentliches MitgliedEquals()
(in der Praxis wäre eine vollständigeIEquatable
Implementierung erforderlich ), können Sie die folgende Klasse in Ihre Präsentationsebene schreiben:Es stellt sich vielleicht überraschend heraus, dass alles, was wir wirklich von einer Koordinate brauchten, um unser Ziel zu erreichen, die Gleichheitsprüfung war! Diese Lösung ist natürlich auf dieses Problem zugeschnitten und geht von einer akzeptablen Speichernutzung / -leistung aus. Es ist nur ein Beispiel, das zu dieser speziellen Problemdomäne passt, und keine Blaupause für eine allgemeine Lösung.
Auch hier wird es unterschiedliche Meinungen darüber geben, ob dies in der Praxis eine unnötige Komplexität ist. In einigen Fällen existiert eine solche Lösung möglicherweise nicht, oder sie ist unheimlich oder komplex. In diesem Fall können Sie auf die obigen drei zurückgreifen.
quelle
Customer
Klasse verhalten , wenn sie ihre Telefonnummer ändern kann? Möglicherweise ändert sich die Telefonnummer des Kunden, und ich muss diese Änderung in der Datenbank beibehalten, aber nichts davon liegt in der Verantwortung eines verhaltensliefernden Domänenobjekts. Das ist ein Problem des Datenzugriffs und würde wahrscheinlich mit einem DTO und beispielsweise einem Repository behandelt werden.Customer
die Daten des Domänenobjekts relativ aktuell zu halten (synchron mit der Datenbank), muss der Lebenszyklus verwaltet werden. Dies liegt ebenfalls nicht in seiner eigenen Verantwortung. Dies würde wahrscheinlich wiederum in einem Repository, einer Fabrik oder einem IOC-Container oder enden was auch immer instanziiertCustomer
s.Die einfachste Möglichkeit, Setter zu vermeiden, besteht darin, die Werte beim Hochfahren
new
des Objekts an die Konstruktormethode zu übergeben . Dies ist auch das übliche Muster, wenn Sie ein Objekt unveränderlich machen möchten . Das heißt, die Dinge sind in der realen Welt nicht immer so klar.Es ist richtig, dass es bei Methoden um Verhalten geht. Einige Objekte, wie z. B. der Kunde, dienen jedoch in erster Linie der Speicherung von Informationen. Das sind die Arten von Objekten, die am meisten von Gettern und Setzern profitieren. Wären solche Methoden überhaupt nicht nötig, würden wir sie einfach ganz beseitigen.
Weitere Lektüre
Wann sind Getter und Setter gerechtfertigt?
quelle
setEvil(null);
Es ist vollkommen in Ordnung, ein Objekt zu haben, das eher Daten als Verhalten verfügbar macht. Wir nennen es einfach ein "Datenobjekt". Das Muster existiert unter Namen wie Data Transfer Object oder Value Object. Wenn der Zweck des Objekts darin besteht, Daten zu speichern, sind Getter und Setter für den Zugriff auf die Daten gültig.
Also warum sollte jemand sagen „Getter und Setter - Methoden sind böse“? Sie werden viel sehen - jemand nimmt eine Richtlinie, die in einem bestimmten Kontext vollkommen gültig ist, und entfernt dann den Kontext, um eine schlagkräftigere Überschrift zu erhalten. Zum Beispiel ist " Komposition vor Vererbung bevorzugen " ein gutes Prinzip, aber bald wird jemand den Kontext entfernen und " Warum erweitert ist böse " (hey, derselbe Autor, was für ein Zufall!) Oder " Vererbung ist böse und muss böse sein " schreiben zerstört ".
Wenn Sie sich den Inhalt des Artikels ansehen, der tatsächlich einige gültige Punkte enthält, wird der Punkt nur gestreckt, um eine Click-Baity-Überschrift zu erstellen. In dem Artikel heißt es beispielsweise, dass Implementierungsdetails nicht offen gelegt werden sollten. Dies sind die Prinzipien der Kapselung und des Versteckens von Daten, die für OO von grundlegender Bedeutung sind. Eine Getter-Methode legt jedoch per Definition keine Implementierungsdetails offen. Bei einem Kundendatenobjekt sind die Eigenschaften von Name , Adresse usw. keine Implementierungsdetails, sondern der gesamte Zweck des Objekts und sollten Teil der öffentlichen Schnittstelle sein.
Lesen Sie die Fortsetzung des Artikels, auf den Sie verweisen, um zu sehen, wie er vorschlägt, Eigenschaften wie "Name" und "Gehalt" für ein "Mitarbeiter" -Objekt ohne die Verwendung der bösen Setter festzulegen. Es stellte sich heraus, dass er ein Muster mit einem 'Exporter' verwendet, der mit den Methoden add Name, add Salary gefüllt ist, die wiederum gleichnamige Felder setzen andere Namenskonvention.
Dies ist wie der Gedanke, dass Sie die Fallstricke von Singletons vermeiden, indem Sie sie nur in Dinge umbenennen, während Sie die gleiche Implementierung beibehalten.
quelle
Um die
Customer
-Klasse aus einem Datenobjekt zu transformieren , können wir uns folgende Fragen zu den Datenfeldern stellen:Wie wollen wir {Datenfeld} verwenden? Wo wird {Datenfeld} verwendet? Kann und soll die Verwendung von {Datenfeld} in die Klasse verschoben werden?
Z.B:
Was ist der Zweck von
Customer.Name
?Mögliche Antworten, Anzeigen des Namens auf einer Anmeldeseite, Verwenden des Namens in Mailings an den Kunden.
Was zu Methoden führt:
Was ist der Zweck von
Customer.DOB
?Bestätigung des Alters des Kunden. Rabatte am Geburtstag des Kunden. Mailings.
In Anbetracht der Kommentare ist das Beispielobjekt
Customer
- sowohl als Datenobjekt als auch als "reales" Objekt mit eigener Verantwortung - zu weit gefasst. dh es hat zu viele Eigenschaften / Verantwortlichkeiten. Was dazu führt, dass entweder viele Komponenten abhängig sindCustomer
(indem man ihre Eigenschaften liest) oder viele Komponenten abhängenCustomer
. Vielleicht gibt es unterschiedliche Sichtweisen des Kunden, vielleicht sollte jede ihre eigene Klasse 1 haben :Der Kunde im Rahmen von
Account
Geldgeschäften wird wahrscheinlich nur verwendet, um:Account
s.Dieser Kunde braucht keine Felder wie
DOB
,FavouriteColour
,Tel
, und vielleicht nicht einmalAddress
.Der Kunde im Kontext eines Benutzers, der sich auf einer Banking-Website anmeldet.
Relevante Felder sind:
FavouriteColour
, die in Form von personalisierten Themen kommen könnte;LanguagePreferences
, undGreetingName
Anstelle von Eigenschaften mit Gettern und Setzern können diese in einer einzigen Methode erfasst werden:
Der Kunde im Rahmen von Marketing und personalisiertem Mailing.
Hierbei wird nicht auf die Eigenschaften eines Datenobjekts zurückgegriffen, sondern von den Verantwortlichkeiten des Objekts ausgegangen. z.B:
Die Tatsache, dass dieses Kundenobjekt eine
FavouriteColour
Eigenschaft und / oder eineAddress
Eigenschaft hat, spielt keine Rolle mehr: Vielleicht verwendet die Implementierung diese Eigenschaften. Möglicherweise werden jedoch auch einige Techniken des maschinellen Lernens verwendet und vorherige Interaktionen mit dem Kunden genutzt, um herauszufinden, an welchen Produkten der Kunde interessiert sein könnte.1. Natürlich sind die
Customer
undAccount
waren Klassen Beispiele und für ein einfaches Beispiel oder Hausübung, Splitting dieser Kunde könnte zu viel des Guten, aber mit dem Beispiel der Spaltung, ich hoffe , zu zeigen , dass das Verfahren ein Datenobjekt in ein Objekt des Drehens mit Verantwortlichkeiten werden funktionieren.quelle
Customer.FavoriteColor
?TL; DR
Verhaltensmodellierung ist gut.
Modellierung für gute (!) Abstraktionen ist besser.
Manchmal sind Datenobjekte erforderlich.
Verhalten und Abstraktion
Es gibt mehrere Gründe, um Getter und Setter zu vermeiden. Wie Sie bereits bemerkt haben, besteht eine darin, die Modellierung von Daten zu vermeiden. Dies ist eigentlich der kleinere Grund. Der größere Grund ist die Abstraktion.
In Ihrem Beispiel mit dem Bankkonto ist klar: Eine
setBalance()
Methode wäre wirklich schlecht, da das Festlegen eines Guthabens nicht das ist, wofür ein Konto verwendet werden sollte. Das Verhalten des Kontos sollte so weit wie möglich von seinem aktuellen Kontostand abweichen. Es kann den Kontostand berücksichtigen, wenn entschieden wird, ob eine Auszahlung fehlschlägt, und es kann Zugriff auf den aktuellen Kontostand gewähren. Wenn Sie jedoch die Interaktion mit einem Bankkonto ändern, muss der Benutzer den neuen Kontostand nicht berechnen. Das sollte der Account selbst tun.Auch ein Paar von
deposit()
undwithdraw()
Methoden ist nicht ideal, um ein Bankkonto zu modellieren. Besser wäre es, nur einetransfer()
Methode anzugeben, die einen anderen Account und einen Betrag als Argumente berücksichtigt. Auf diese Weise kann die Kontoklasse trivial sicherstellen, dass Sie nicht versehentlich Geld in Ihrem System anlegen / vernichten. Sie bietet eine sehr nützliche Abstraktion und bietet den Benutzern tatsächlich mehr Einblicke, da die Verwendung spezieller Konten erzwungen wird verdientes / investiertes / verlorenes Geld (siehe Doppelabrechnung ). Natürlich benötigt nicht jede Verwendung eines Kontos diese Abstraktionsebene, aber es lohnt sich auf jeden Fall zu überlegen, wie viel Abstraktion Ihre Klassen bieten können.Beachten Sie, dass das Bereitstellen der Abstraktion und das Ausblenden von Dateninternalen nicht immer dasselbe ist. Fast jede Anwendung enthält Klassen, die im Grunde genommen nur Daten sind. Tupel, Wörterbücher und Arrays sind häufige Beispiele. Sie möchten die x-Koordinate eines Punkts nicht vor dem Benutzer verbergen. Es gibt sehr wenig Abstraktion, die Sie mit einem Punkt machen können / sollten.
Die Kundenklasse
Ein Kunde ist sicherlich eine Entität in Ihrem System, die versuchen sollte, nützliche Abstraktionen bereitzustellen. Zum Beispiel sollte es wahrscheinlich mit einem Einkaufswagen verbunden sein, und die Kombination aus Einkaufswagen und Kunde sollte es ermöglichen, einen Kauf zu tätigen, der Aktionen wie das Senden der angeforderten Produkte und das Aufladen von Geld (unter Berücksichtigung seiner ausgewählten Zahlung) auslösen kann Methode) usw.
Der Haken ist, dass alle Daten, die Sie erwähnt haben, nicht nur einem Kunden zugeordnet sind, alle diese Daten sind auch veränderlich. Der Kunde darf umziehen. Sie können ihre Kreditkartenfirma ändern. Sie können ihre E-Mail-Adresse und Telefonnummer ändern. Verdammt, sie können sogar ihren Namen und / oder ihr Geschlecht ändern! In der Tat muss eine Kundenklasse mit vollem Funktionsumfang vollständigen Änderungszugriff auf alle diese Datenelemente bieten.
Dennoch können / sollten die Setter nicht-triviale Dienste bereitstellen: Sie können das korrekte Format von E-Mail-Adressen, die Überprüfung von Postadressen usw. sicherstellen. Ebenso können die "Getters" hochrangige Dienste bereitstellen, wie das Bereitstellen von E-Mail-Adressen im
Name <[email protected]>
Format Verwenden Sie die Namensfelder und die hinterlegte E-Mail-Adresse oder geben Sie eine korrekt formatierte Postanschrift usw. an. Welche dieser Funktionen sinnvoll ist, hängt natürlich stark von Ihrem Anwendungsfall ab. Es könnte ein völliger Overkill sein oder es könnte eine andere Klasse erfordern, um es richtig zu machen. Die Wahl der Abstraktionsebene ist nicht einfach.quelle
Beim Versuch, Kaspers Antwort zu erweitern, ist es am einfachsten, gegen Setter zu schimpfen und sie zu eliminieren. In einem ziemlich vagen, handwedelnden (und hoffentlich humorvollen) Argument:
Wann würde sich Customer.Name jemals ändern?
Selten. Vielleicht haben sie geheiratet. Oder ging in den Zeugenschutz. In diesem Fall möchten Sie jedoch auch den Wohnort, die nächsten Verwandten und andere Informationen überprüfen und möglicherweise ändern.
Wann würde sich DOB jemals ändern?
Nur bei der ersten Erstellung oder bei einer Dateneingabe. Oder wenn sie ein Domincan Baseballspieler sind. :-)
Diese Felder sollten für normale Routineeinsteller nicht zugänglich sein. Möglicherweise haben Sie eine
Customer.initialEntry()
Methode oder eineCustomer.screwedUpHaveToChange()
Methode, die spezielle Berechtigungen erfordert. Aber keine öffentlicheCustomer.setDOB()
Methode.Normalerweise wird ein Kunde aus einer Datenbank, einer REST-API oder XML gelesen. Haben Sie eine Methode
Customer.readFromDB()
, oder, wenn Sie strenger in Bezug auf SRP / Trennung von Bedenken sind, hätten Sie einen separaten Builder, z. B. einCustomerPersister
Objekt mit einerread()
Methode. Intern setzen sie irgendwie die Felder (ich bevorzuge die Verwendung des Paketzugriffs oder einer inneren Klasse, YMMV). Aber auch hier vermeiden Sie öffentliche Setter.(Nachtrag als Frage hat sich etwas geändert ...)
Nehmen wir an, Ihre Anwendung verwendet stark relationale Datenbanken. Es wäre töricht , haben
Customer.saveToMYSQL()
oderCustomer.readFromMYSQL()
Methoden. Dies führt zu einer unerwünschten Kopplung an eine konkrete, nicht standardisierte Einheit , die sich wahrscheinlich ändert . Zum Beispiel, wenn Sie das Schema ändern oder zu Postgress oder Oracle wechseln.Allerdings IMO, es ist durchaus akzeptabel zu koppeln Kunden zu einem abstrakten Standard ,
ResultSet
. Ein separates Hilfsobjekt (ich nenne esCustomerDBHelper
wahrscheinlich eine Unterklasse vonAbstractMySQLHelper
) kennt alle komplexen Verbindungen zu Ihrer Datenbank, kennt die kniffligen Optimierungsdetails, kennt die Tabellen, Abfragen, Verknüpfungen usw. (oder verwendet a) ORM wie Ruhezustand), um das ResultSet zu generieren. Ihr Objekt spricht mit demResultSet
, was ist eine abstrakte Norm , kaum zu ändern. Wenn Sie die zugrunde liegende Datenbank oder das Schema ändern , ändert sich Customer nicht , der CustomerDBHelper jedoch. Wenn Sie Glück haben, ändert sich nur AbstractMySQLHelper, das die Änderungen automatisch für Kunden, Händler, Versand usw. vornimmt.Auf diese Weise können Sie (vielleicht) die Notwendigkeit von Gettern und Setzern vermeiden oder verringern.
Und, der Hauptpunkt des Holub-Artikels, vergleichen und kontrastieren Sie das Obige damit, wie es wäre, wenn Sie Getter und Setter für alles verwenden und die Datenbank ändern würden.
Angenommen, Sie verwenden viel XML. IMO, es ist in Ordnung, Ihren Kunden an einen abstrakten Standard zu koppeln, z. B. an einen Python- xml.etree.ElementTree oder ein Java- org.w3c.dom.Element . Der Kunde bekommt und setzt sich davon ab. Auch hier können Sie (vielleicht) die Notwendigkeit von Gettern und Setzern verringern.
quelle
Das Problem mit Gettern und Setzern kann darin bestehen, dass eine Klasse in der Geschäftslogik auf eine Weise verwendet werden kann, aber Sie können auch Hilfsklassen zum Serialisieren / Deserialisieren der Daten aus einer Datenbank oder Datei oder einem anderen dauerhaften Speicher haben.
Aufgrund der Tatsache, dass es viele Möglichkeiten zum Speichern / Abrufen Ihrer Daten gibt und Sie die Datenobjekte von der Art und Weise entkoppeln möchten, in der sie gespeichert sind, kann die Kapselung "kompromittiert" werden, indem diese Mitglieder entweder öffentlich gemacht oder durch Getter und zugänglich gemacht werden Setter, was fast so schlimm ist, wie sie öffentlich zu machen.
Es gibt verschiedene Möglichkeiten, dies zu umgehen. Eine Möglichkeit besteht darin, die Daten einem "Freund" zur Verfügung zu stellen. Obwohl die Freundschaft nicht vererbt wird, kann dies durch einen beliebigen Serialisierer überwunden werden, der die Informationen vom Freund anfordert, dh der Basisserialisierer "leitet" die Informationen weiter.
Ihre Klasse könnte eine generische "fromMetadata" - oder "toMetadata" -Methode haben. Aus-Metadaten konstruieren ein Objekt, so dass es durchaus ein Konstruktor sein kann. Wenn es sich um eine dynamisch typisierte Sprache handelt, sind Metadaten für eine solche Sprache ziemlich normal und wahrscheinlich die wichtigste Methode zum Erstellen solcher Objekte.
Wenn Ihre Sprache speziell C ++ ist, besteht eine Möglichkeit darin, eine öffentliche "Struktur" von Daten zu haben und dann für Ihre Klasse eine Instanz dieser "Struktur" als Mitglied zu haben und tatsächlich alle Daten, die Sie speichern werden. abrufen, um darin gespeichert zu werden. Sie können dann problemlos "Wrapper" schreiben, um Ihre Daten in mehreren Formaten zu lesen / schreiben.
Wenn Ihre Sprache C # oder Java ist, die keine "Strukturen" haben, können Sie dies auf ähnliche Weise tun, aber Ihre Struktur ist jetzt eine Sekundärklasse. Es gibt kein wirkliches Konzept für "Besitz" der Daten oder Konstanz. Wenn Sie also eine Instanz der Klasse mit Ihren Daten herausgeben und alles öffentlich ist, kann alles, was in den Besitz kommt, diese ändern. Sie könnten es "klonen", obwohl dies teuer sein kann. Alternativ können Sie dafür sorgen, dass diese Klasse private Daten enthält, aber Accessoren verwendet. Dies gibt Benutzern Ihrer Klasse einen Umweg, um an die Daten zu gelangen, aber es ist nicht die direkte Schnittstelle zu Ihrer Klasse und ist wirklich ein Detail beim Speichern der Daten der Klasse, was auch ein Anwendungsfall ist.
quelle
Bei OOP geht es darum, Verhalten in Objekten zu kapseln und zu verbergen. Objekte sind Black Boxes. Dies ist ein Weg, um etwas zu entwerfen. Das Asset besteht in vielen Fällen darin, dass man den internen Zustand einer anderen Komponente nicht kennen muss und es besser ist, es nicht wissen zu müssen. Sie können diese Idee hauptsächlich mit Schnittstellen oder innerhalb eines Objekts mit Sichtbarkeit erzwingen und darauf achten, dass dem Aufrufer nur zulässige Verben / Aktionen zur Verfügung stehen.
Dies funktioniert gut für irgendeine Art von Problem. Zum Beispiel in Benutzeroberflächen zur Modellierung einzelner UI-Komponenten. Wenn Sie mit einem Textfeld arbeiten, möchten Sie nur den Text festlegen, abrufen oder das Textänderungsereignis anhören. Sie interessieren sich normalerweise nicht dafür, wo sich der Cursor befindet, welche Schriftart zum Zeichnen des Texts verwendet wird oder wie die Tastatur verwendet wird. Die Verkapselung bietet hier eine Menge.
Im Gegenteil, wenn Sie einen Netzwerkdienst anrufen, geben Sie eine explizite Eingabe ein. In der Regel gibt es eine Grammatik (wie in JSON oder XML) und alle Optionen zum Aufrufen des Dienstes haben keinen Grund, ausgeblendet zu werden. Sie können den Dienst so aufrufen, wie Sie möchten, und das Datenformat ist öffentlich und veröffentlicht.
In diesem oder vielen anderen Fällen (wie dem Zugriff auf eine Datenbank) arbeiten Sie wirklich mit gemeinsam genutzten Daten. Als solches gibt es keinen Grund, es zu verbergen, im Gegenteil, Sie möchten es zur Verfügung stellen. Es kann Bedenken hinsichtlich des Lese- / Schreibzugriffs oder der Datenüberprüfungskonsistenz geben, in diesem Kern jedoch das Kernkonzept, wenn dies öffentlich ist.
Für solche Entwurfsanforderungen, bei denen Sie die Verkapselung vermeiden und die Dinge öffentlich machen und im Klaren sein möchten, möchten Sie Objekte vermeiden. Was Sie wirklich brauchen, sind Tupel, C-Strukturen oder deren Äquivalente, keine Objekte.
Es kommt aber auch in Sprachen wie Java vor. Sie können nur Objekte oder Arrays von Objekten modellieren. Objekte selbst können einige native Typen enthalten (int, float ...), aber das ist alles. Objekte können sich aber auch wie eine einfache Struktur mit nur öffentlichen Feldern und so weiter verhalten.
Wenn Sie also Daten modellieren, können Sie nur öffentliche Felder in Objekten verwenden, da Sie nicht mehr benötigen. Sie verwenden keine Kapselung, weil Sie sie nicht benötigen. Dies geschieht auf diese Weise in vielen Sprachen. In Java gab es in der Vergangenheit eine Standard-Rose, bei der Sie mit Getter / Setter zumindest Lese- / Schreibrechte haben konnten (indem Sie zum Beispiel keinen Setter hinzufügten), und die Tools und das Framework mithilfe der Instrospection-API nach Getter / Setter-Methoden suchten und diese verwendeten Füllen Sie den Inhalt automatisch aus oder zeigen Sie diese als veränderbare Felder in der automatisch generierten Benutzeroberfläche an.
Dort auch das Argument, dass Sie einige Logik / Prüfung in der Setter-Methode hinzufügen könnten.
In der Realität gibt es fast keine Rechtfertigung für Getter / Setter, da diese am häufigsten zur Modellierung reiner Daten verwendet werden. Frameworks und Entwickler, die Ihre Objekte verwenden, erwarten, dass der Getter / Setter ohnehin nichts weiter tut, als die Felder zu setzen / abzurufen. Sie tun effektiv nicht mehr mit Getter / Setter als mit öffentlichen Feldern.
Aber das ist alte Gewohnheiten und alte Gewohnheiten sind schwer zu entfernen ... Sie könnten sogar von Ihren Kollegen oder Lehrern bedroht werden, wenn Sie Getter / Setter nicht blind überall einsetzen, wenn ihnen der Hintergrund fehlt, um besser zu verstehen, was sie sind und was sie sind nicht.
Sie müssten wahrscheinlich die Sprache ändern, um den Code für alle diese Getters / Setters-Boilerplate zu erhalten. (Wie C # oder Lispeln). Für mich sind Getter nur ein weiterer Fehler von einer Milliarde Dollar ...
quelle
@Getter @Setter class MutablePoint3D {private int x, y, z;}
.Ich denke, diese Frage ist stachelig, weil Sie sich Sorgen über Verhaltensmethoden zum Auffüllen von Daten machen, aber ich sehe keinen Hinweis darauf, welches Verhalten die
Customer
Klasse von Objekten verkapseln soll.Verwechseln Sie nicht
Customer
als Klasse von Objekten mit ‚Kunden‘ als Benutzer / Schauspieler , die verschiedene Aufgaben mit der Software durchführt.Wenn Sie von einer Kundenklasse sprechen, die eine Menge Informationen über den Kunden enthält, dann sieht es so aus, als ob Ihre Kundenklasse ihn kaum von einem Stein unterscheidet. A
Rock
kann eine Farbe haben, Sie können ihm einen Namen geben, Sie können ein Feld zum Speichern der aktuellen Adresse haben, aber wir erwarten kein intelligentes Verhalten von einem Stein.Aus dem verlinkten Artikel über Getter / Setter, die böse sind:
Wenn Sie auf einen Stein als "a" verweisen,
Customer
ändert dies nichts an der Tatsache, dass es sich nur um ein Objekt mit einigen Eigenschaften handelt, die Sie verfolgen möchten, und es spielt keine Rolle, welche Tricks Sie spielen möchten, um sich von Gettern und zu entfernen Setter. Einem Stein ist es egal, ob er einen gültigen Namen hat und von einem Stein würde nicht erwartet, dass er weiß, ob eine Adresse gültig ist oder nicht.Ihr Bestellsystem könnte eine
Rock
mit einer Bestellung verknüpfen. SolangeRock
eine Adresse definiert ist, kann ein Teil des Systems sicherstellen, dass ein Artikel an einen Stein geliefert wird.In all diesen Fällen
Rock
ist das nur ein Datenobjekt und wird es auch weiterhin sein, bis wir spezifische Verhaltensweisen mit nützlichen Ergebnissen anstelle von Hypothesen definieren.Versuche dies:
Wenn Sie vermeiden, das Wort "Kunde" mit zwei potenziell unterschiedlichen Bedeutungen zu überladen, sollte dies die Konzeptualisierung vereinfachen.
Hat ein
Rock
Objekt eine Bestellung aufgeben oder ist das etwas , das ein Mensch tut , indem Sie auf UI - Elemente klicken Aktionen in Ihrem System auszulösen?quelle
Ich füge hier meine 2 Cent hinzu und erwähne den Ansatz von SQL-sprechenden Objekten .
Dieser Ansatz basiert auf dem Begriff des in sich geschlossenen Objekts. Es verfügt über alle Ressourcen, um sein Verhalten umzusetzen. Es muss nicht erklärt werden, wie es zu tun ist - deklarative Anfrage ist genug. Und ein Objekt muss definitiv nicht alle Daten als Klasseneigenschaften enthalten. Es ist wirklich egal - und sollte es auch nicht sein - woher sie kommen.
Apropos Aggregat : Unveränderlichkeit ist auch kein Thema. Angenommen, Sie haben eine Folge von Zuständen, die das Aggregat enthalten kann: Es ist völlig in Ordnung, jeden Zustand als eigenständiges Objekt zu implementieren. Möglicherweise könnten Sie sogar noch weiter gehen: Plaudern Sie mit Ihrem Domain-Experten. Wahrscheinlich sieht er oder sie dieses Aggregat nicht als eine einheitliche Einheit. Wahrscheinlich hat jeder Staat seine eigene Bedeutung und verdient sein eigenes Objekt.
Abschließend möchte ich darauf hinweisen, dass der Objektfindungsprozess bei der Systemzerlegung in Subsysteme sehr ähnlich ist . Beides basiert auf Verhalten, nichts anderes.
quelle