Kann das Umbenennen einer Methode die Kapselung bewahren?

9

Ich habe diese Seite gelesen , ungefähr wann Getter / Setter gerechtfertigt sind, und das OP hat das folgende Codebeispiel gegeben:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

Die akzeptierte Antwort lautet:

By the way, in Ihrem Beispiel, würde ich die Klasse gibt Fridgedie putCheese()und takeCheese()Methoden, statt get_cheese() und set_cheese(). Dann hätten Sie noch Kapselung.

Wie wird die Kapselung erhalten, indem sie von get / set in putCheese()/ umbenannt wird takeCheese()? Sie erhalten / setzen offensichtlich einen Wert. Warum also nicht einfach als get / set belassen?

In derselben Antwort heißt es auch:

Getter und Setter zu haben, bricht an sich nicht die Kapselung. Die Unterbrechungskapselung fügt automatisch einen Getter und einen Setter für jedes Datenelement (jedes Feld in Java-Jargon) hinzu, ohne darüber nachzudenken.

In diesem Fall haben wir nur eine Variable, cheeseund Sie können den Käse nehmen und in den Kühlschrank zurückgeben, sodass in diesem Fall ein Get / Set-Paar gerechtfertigt ist.

Königin Swetlana
quelle
7
putCheesewürde dem Kühlschrank Käse hinzufügen und takeCheeseihn entfernen - dies sind (übergeordnete) domänenorientierte Abstraktionen und keine Objektfeld-Getter und -Setter (die (untergeordnete) Computerprogrammierungsabstraktionen sind).
Erik Eidt

Antworten:

17

Ich denke, Sie verpassen den Punkt. Es heißt nicht, dass Sie den Setter und Getter umbenennen sollten, sondern Methoden, mit denen Sie Gegenstände zum Kühlschrank hinzufügen und daraus entfernen können. dh

public class Fridge
{
    private int numberOfCheeseSlices;

    public void AddCheeseSlices(int n)
    {
         if(n < 0) { 
             throw new Exception("you cant add negative cheese!");
         }
         if(numberOfCheeseSlices + n > this.capacityOfFridge) { 
             throw new Exception("TOO MUCH CHEESE!!");
         }
         ..etc
         this.numberOfCheeseSlices += n;
    }
}

Jetzt ist die private Variable gekapselt. Ich kann es nicht einfach auf alles einstellen, was ich will, ich kann nur Käsescheiben mit den Methoden hinzufügen und aus dem Kühlschrank entfernen, die wiederum sicherstellen, dass alle von mir gewünschten Regeln für die Käsegeschäftslogik angewendet werden.

Ewan
quelle
2
Richtig, aber Sie können es trotzdem so lassen, als setCheese()hätten Sie dieselbe Logik im Setter. Für mich jedenfalls versuchen Sie, die Tatsache zu verbergen, dass Sie einen Setter verwenden, indem Sie die Methode umbenennen, aber Sie haben offensichtlich etwas.
QueenSvetlana
21
@ QueenSvetlana es ist kein Setter. Es ist eine Operation. Sie sagen dem Kühlschrank, "hier ist noch ein Käse", und er fügt ihn seiner internen, eingekapselten Käsezahl hinzu. Ein Setter würde sagen "Sie haben jetzt 5 Käse." Dies sind funktional unterschiedliche Operationen, nicht nur unterschiedliche Namen.
Ant P
1
man könnte es setCheese nennen, aber das wäre verwirrend, da es den Wert des Käses nicht einstellen würde.
Ewan
6
Beachten Sie, dass hier ein ganzzahliger Überlauffehler vorliegt. Wenn es bereits mehr als 0 Käse gibt, AddCheese(Integer.MAX_VALUE)sollte dies fehlschlagen, wird es aber nicht.
user253751
3
das würde insgesamt in der ..etc Abschnitt behandelt werden
Ewan
5

Getter und Setter unterbrechen per Definition jedes Mal die Kapselung . Was könnte argumentiert werden, dass manchmal müssen wir das tun. Hier sind meine Antworten:

Wie wird die Kapselung erhalten, indem sie von get / set in putCheese () / takeCheese () umbenannt wird? Sie erhalten / setzen offensichtlich einen Wert. Warum lassen Sie ihn nicht einfach als get / set?

Der Unterschied liegt in der Semantik, dh in der Bedeutung dessen, was Sie schreiben. Bei der Kapselung geht es nicht nur darum, den inneren Zustand zu schützen, sondern ihn zu verbergen . Der interne Status sollte nicht einmal außerhalb bekannt sein. Stattdessen wird erwartet, dass das Objekt geschäftsrelevante Methoden (manchmal als "Verhalten" bezeichnet) zur Manipulation / Verwendung dieses Status bietet.

Also getund setsind Fachbegriffe und scheinen nichts mit der "Kühlschrank" -Domäne zu tun zu haben, während putund takekönnten tatsächlich einen Sinn ergeben.

Jedoch , ob putoder takemacht tatsächlicher noch Sinn von den Anforderungen abhängt und nicht objektiv beurteilt werden kann. Siehe nächster Punkt:

In diesem Fall haben wir nur eine Variable, Käse, und Sie können den Käse nehmen und in den Kühlschrank zurückgeben, daher ist in diesem Fall ein Get / Set-Paar gerechtfertigt.

Das kann ohne mehr Kontext nicht objektiv bestimmt werden. Ihr Beispiel enthält eine go_shopping()andere Methode. Wenn das ist , was das Fridgeist für, als sie es nicht brauchen getoder set, was es braucht , ist Fridge.go_shopping(). Auf diese Weise haben Sie eine Methode, die aus den Anforderungen abgeleitet wird. Alle benötigten "Daten" sind lokal und Sie haben tatsächliches Verhalten anstelle einer nur dünn verschleierten Datenstruktur.

Denken Sie daran, dass Sie kein generisches, wiederverwendbares Produkt erstellen Fridge. Sie bauen ein nur Fridgefür Ihre Anforderungen. Jeder Aufwand, der aufgewendet wird, um mehr als das zu erreichen, was es sein muss, ist tatsächlich verschwenderisch.

Robert Bräutigam
quelle
10
Getters and setters break encapsulation every single time- Das ist etwas zu stark für meinen Geschmack formuliert. Methoden (einschließlich Getter und Setter) haben den gleichen Zweck wie Gates bei einem Konzert: Sie ermöglichen den ordnungsgemäßen Ein- und Ausstieg aus dem Veranstaltungsort.
Robert Harvey
8
"Getter und Setter unterbrechen die Kapselung jedes Mal per Definition" - nach welcher Definition? Auch ich habe ein Problem damit, weil Getter und Setter wie jedes andere öffentliche Mitglied nur ein Teil der öffentlichen Schnittstelle sind. Sie unterbrechen die Kapselung nur, wenn sie Implementierungsdetails offenlegen, die vom Entwurf her als intern angesehen werden (dh durch eine Entscheidung des Programmierers (oder eines Teams)). Die Tatsache, dass Getter und Setter oft willkürlich hinzugefügt werden, wobei die Kopplungskontrolle ein nachträglicher Gedanke ist, ist eine ganz andere Sache.
Filip Milovanović
2
@ FilipMilovanović Messepunkt. Wie ich in der Antwort erwähne, wird "Kapselung" verwendet, um Objektinternale (== Objektstatus == Instanzvariablen) auszublenden . Getter und Setter veröffentlichen Objektinternale. Nach diesen Definitionen stehen sie daher in ständigem Konflikt. Ob wir jetzt manchmal die Kapselung unterbrechen müssen , ist eine andere Sache, die separat behandelt werden sollte. Um klar zu sein, wenn ich sage, dass Setter / Getter immer die Kapselung brechen, sage ich nicht, dass es immer "falsch" ist, wenn das hilft.
Robert Bräutigam
5
Getter und Setter "brechen die Kapselung nicht buchstäblich immer". Getter und Setter beziehen sich oft nicht einmal direkt auf Mitglieder darunter, führen eine Operation aus oder sind nur exklusiv definiert. Möglicherweise haben Sie nur einen Getter oder nur einen Setter. Die Kapselung wird unterbrochen, wenn Getter und Setter nur den Zugriff auf Mitglieder ausführen und beide für dieselben Felder definiert sind. Dies kann auch für Bibliotheksverwalter erforderlich sein, die ABIs / APIs nicht mit internen Änderungen beschädigen und eine Neukompilierung des Clients vermeiden möchten.
Am
4
Ok, Getter und Setter brechen die Kapselung nur 99% der Zeit. Glücklich? Und indem sie den Typ des gekapselten Objekts verfügbar machen, brechen sie definitiv die Kapselung. Sobald Sie eine haben, ist public int getFoo()es in der Praxis fast unmöglich, zu einem anderen Typ zu wechseln.
user949300
3

Fast alles zeigt ein grundlegendes Missverständnis der Kapselung und ihrer Anwendung.

Die anfängliche Antwort, dass Sie die Kapselung unterbrochen haben, ist einfach falsch. Bei Ihrer Anwendung muss möglicherweise einfach der Wert des Käses im Kühlschrank eingestellt werden, anstatt ihn zu erhöhen / verringern oder hinzuzufügen / zu entfernen. Es ist auch keine Semantik, egal wie Sie es nennen. Wenn Sie auf Attribute zugreifen und / oder diese ändern müssen, unterbrechen Sie die Kapselung nicht, indem Sie sie bereitstellen. Schließlich geht es bei der Kapselung nicht wirklich darum, sich zu "verstecken", sondern darum, den Zugriff auf Status und Werte zu steuern, die außerhalb der Klasse nicht öffentlich sein oder manipuliert werden müssen, während sie denjenigen gewährt werden, die die intern zur Verfügung gestellte Aufgabe ausführen sollen.

Ein Getter oder Setter unterbricht die Kapselung nicht, wenn ein legitimer Bedarf besteht, einen Wert abzurufen oder festzulegen. Deshalb können Methoden veröffentlicht werden.

Bei der Kapselung geht es darum, Daten und die Methoden, mit denen diese Daten direkt geändert werden, an einem logischen Ort, der Klasse, zusammenzuhalten.

In diesem speziellen Fall besteht eindeutig die Notwendigkeit, den Wert von Käse in der Anwendung zu ändern. Unabhängig davon, wie dies erfolgt, über get / set oder add / remove, solange die Methoden in der Klasse gekapselt sind, folgen Sie dem objektorientierten Stil.

Zur Verdeutlichung werde ich ein Beispiel geben, wie die Kapselung durch Bereitstellung des Zugriffs unabhängig vom Methodennamen oder der logischen Ausführung unterbrochen wird.

Angenommen, Ihr Kühlschrank hat eine "Lebensdauer", nur einige Zecken, bevor der Kühlschrank nicht mehr betriebsbereit ist (aus Gründen der Argumentation kann der Kühlschrank nicht repariert werden). Logischerweise kann ein Benutzer (oder der Rest Ihrer Anwendung) diesen Wert auf keinen Fall ändern. Es sollte privat sein. Es wäre nur durch ein anderes öffentliches Attribut sichtbar, das als "isWorking" bekannt ist. Wenn die Lebensdauer abläuft, ist der Kühlschrank intern auf false eingestellt.

Die Ausführung des Countdowns für die Lebensdauer und das Umlegen des isWorking-Schalters erfolgt vollständig im Kühlschrank. Nichts außerhalb könnte / sollte den Prozess beeinflussen können. isWorking sollte nur sichtbar sein, damit ein Getter die Kapselung nicht unterbricht. Das Hinzufügen von Accessoren für Elemente des Lebensdauerprozesses würde jedoch Ihre Kapselung beschädigen.

Wie die meisten Dinge ist die Definition der Kapselung nicht wörtlich, sondern relativ. Sollten Sie X außerhalb der Klasse sehen können? Sollten Sie in der Lage sein, Y zu ändern? Ist alles, was für Ihr Objekt hier in dieser Klasse gilt, oder ist die Funktionalität auf mehrere Klassen verteilt?

user343330
quelle
Schauen wir es uns pragmatisch an. Sie sagen, dass die Kapselung nicht unterbrochen wird, wenn der Code auf diese Weise beabsichtigt ist oder wenn es einen "legitimen" Grund gibt. Das bedeutet im Grunde, dass wir niemals sagen können, dass die Kapselung unterbrochen ist, weil die Entwicklerin nur sagen muss, dass sie dies "beabsichtigt" hat, oder dass es einen "legitimen" Grund gab. Diese Definition ist also im Grunde genommen nutzlos, nicht wahr?
Robert Bräutigam
Nein, ich sage, die Kapselung wird nicht einfach durch die Bereitstellung des Zugriffs unterbrochen. Und es hat nichts damit zu tun, was ein Programmierer sagt, sondern was die Anforderungen der Anwendung sind. Eine Anwendung muss Zugriff haben, um die Objekte bearbeiten zu können. Bei der Kapselung geht es darum, den Zugriff zu steuern. Eine Anwendung muss möglicherweise ein Attribut eines Objekts "setzen", aber die Logik und / oder Berechnungen, die zum Ausführen dieser "Set" -Operation erforderlich sind, sollten in der Objektklasse gekapselt sein.
user343330
Ich denke, hier unterscheiden wir uns grundlegend. Sie sagen: "Eine Anwendung muss möglicherweise ein Attribut eines Objekts" setzen "...". Das glaube ich nicht. Die Auswahl eines Designs, bei dem ein Attribut festgelegt oder abgerufen wird, liegt beim Entwickler. Es ist eine Wahl! Es gibt viele Möglichkeiten, etwas zu codieren, nicht alle beinhalten das Abrufen oder Festlegen von Instanzvariablenwerten.
Robert Bräutigam
Könnte sein ... Normalerweise basiert das Design darauf, einen Bedarf zu erfüllen. Ob einer vom Programmierer ausgewählt wird, hängt von der Geschäftsumgebung ab. In beiden Fällen müssen Sie diese Funktionalität auf irgendeine Weise zugänglich machen, wenn die Anwendung einen Wert, der Teil eines Objekts ist, grundlegend bearbeiten muss. Wenn Sie einen Einstiegspunkt für die Arbeit bereitstellen, wird die Kapselung nicht unterbrochen. Nehmen Sie eine Verkaufs-App. Irgendwann sollte Ihre Transaktion die Steuer berechnen.
Wenn
Setter sind aufgrund ihrer Einfachheit schlechte Beispiele. Denken Sie an eine Funktion, die viel mehr Arbeit leistet und dazu führt, dass ein Objektattribut aktualisiert wird, während etwas außerhalb der Klasse die Arbeit erledigt und dann object.attribute = x sagt. Die erste ist eine gute Kapselung, während der entsprechende Zugriff bereitgestellt wird, die zweite nicht. Muss die Anwendung nur das Teil eines Objekts kennen, um etwas anderes zu tun, das nicht in der Objektklasse enthalten sein kann? Wenn ja, wird Ihre Kapselung durch das Belichten nicht beschädigt. Wenn nein, dann kapseln Sie es in die Objektklasse
user343330
1

Es geht nicht nur darum, eine Methode umzubenennen. Die beiden Methoden funktionieren unterschiedlich.

(Stellen Sie sich das in Ihrem Kopf vor)

get_cheese und set_cheese legen den Käse frei. putCheese () und takeCheese () halten den Käse versteckt und kümmern sich um die Verwaltung und geben dem Benutzer eine Möglichkeit, damit umzugehen. Der Beobachter sieht den Käse nicht, er sieht nur zwei Methoden, um ihn zu manipulieren.

Daneesha
quelle