it-swarm.dev

Wann sind Getter und Setter gerechtfertigt?

Getter und Setter werden oft als nicht richtig OO kritisiert. Auf der anderen Seite hat der meiste OO Code, den ich gesehen habe, umfangreiche Getter und Setter.

Wann sind Getter und Setter gerechtfertigt? Versuchen Sie, sie zu vermeiden? Werden sie im Allgemeinen überbeansprucht?

Wenn Ihre Lieblingssprache Eigenschaften hat (meine), werden solche Dinge auch als Getter und Setter für diese Frage betrachtet. Sie sind aus der Sicht der OO Methodik) dasselbe. Sie haben nur eine schönere Syntax.

Quellen für Getter/Setter-Kritik (einige stammen aus Kommentaren, um sie besser sichtbar zu machen):

Um die Kritik einfach auszudrücken: Mit Getters und Setter können Sie den internen Zustand von Objekten von außerhalb des Objekts manipulieren. Dies verletzt die Kapselung. Nur das Objekt selbst sollte sich um seinen internen Zustand kümmern.

Und ein Beispiel für eine prozedurale Version des Codes.

struct Fridge
{
    int cheese;
}

void go_shopping(Fridge fridge)
{
     fridge.cheese += 5;
}

Mutator-Version des Codes:

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 Getter und Setter machten den Code viel komplizierter, ohne eine ordnungsgemäße Kapselung zu ermöglichen. Da der interne Status für andere Objekte zugänglich ist, gewinnen wir durch Hinzufügen dieser Getter und Setter nicht viel.

Die Frage wurde bereits zu Stack Overflow diskutiert:

179
Winston Ewert

Getter und Setter zu haben, bricht an sich nicht die Kapselung. Was die Kapselung unterbricht, ist das automatische Hinzufügen eines Getters und eines Setters für jedes Datenelement (jedes Feld, in Java lingo), ohne darüber nachzudenken Besser als alle Datenmitglieder öffentlich zu machen, ist es nur ein kleiner Schritt entfernt.

Der Punkt der Kapselung ist nicht, dass Sie nicht in der Lage sein sollten, den Status des Objekts von außerhalb des Objekts zu kennen oder zu ändern, sondern dass Sie eine vernünftige Richtlinie dafür haben sollten.

  • Einige Datenelemente befinden sich möglicherweise vollständig im Objekt und sollten weder Getter noch Setter haben.

  • Einige Datenelemente sollten schreibgeschützt sein, daher benötigen sie möglicherweise Getter, aber keine Setter.

  • Einige Datenelemente müssen möglicherweise miteinander konsistent gehalten werden. In einem solchen Fall würden Sie nicht für jeden einen Setter bereitstellen, sondern eine einzige Methode, um sie gleichzeitig festzulegen, damit Sie die Werte auf Konsistenz überprüfen können.

  • Einige Datenelemente müssen möglicherweise nur auf eine bestimmte Weise geändert werden, z. B. um einen festen Betrag erhöht oder verringert. In diesem Fall würden Sie anstelle eines Setzers eine Methode increment() und/oder decrement() bereitstellen.

  • Wieder andere müssen möglicherweise tatsächlich lesen und schreiben und hätten sowohl einen Getter als auch einen Setter.

Betrachten Sie ein Beispiel für einen class Person. Angenommen, eine Person hat einen Namen, eine Sozialversicherungsnummer und ein Alter. Nehmen wir an, wir erlauben Menschen nicht, jemals ihren Namen oder ihre Sozialversicherungsnummer zu ändern. Das Alter der Person sollte jedoch jedes Jahr um 1 erhöht werden. In diesem Fall würden Sie einen Konstruktor bereitstellen, der den Namen und die SSN auf die angegebenen Werte initialisiert und das Alter auf 0 initialisiert. Sie würden auch eine Methode incrementAge() bereitstellen, die das Alter erhöht von 1. Sie würden auch Getter für alle drei bereitstellen. In diesem Fall sind keine Setter erforderlich.

In diesem Entwurf erlauben Sie, dass der Status des Objekts von außerhalb der Klasse überprüft und von außerhalb der Klasse geändert wird. Sie erlauben jedoch nicht, dass der Status willkürlich geändert wird. Es gibt eine Richtlinie, die effektiv besagt, dass der Name und die SSN überhaupt nicht geändert werden können und dass das Alter jeweils um 1 Jahr erhöht werden kann.

Nehmen wir jetzt an, eine Person hat auch ein Gehalt. Und die Leute können nach Belieben den Job wechseln, was bedeutet, dass sich auch ihr Gehalt ändert. Um diese Situation zu modellieren, haben wir keine andere Möglichkeit, als eine setSalary() -Methode bereitzustellen! In diesem Fall ist es durchaus sinnvoll, das Gehalt nach Belieben ändern zu lassen.

Übrigens würde ich in Ihrem Beispiel der Klasse Fridge die Methoden putCheese() und takeCheese() anstelle von get_cheese() und set_cheese(). Dann hätten Sie noch Kapselung.


public class Fridge {
  private List objects;
  private Date warranty;

  /** How the warranty is stored internally is a detail. */
  public Fridge( Date warranty ) {
    // The Fridge can set its internal warranty, but it is not re-exposed.
    setWarranty( warranty );
  }

  /** Doesn't expose how the fridge knows it is empty. */
  public boolean isEmpty() {
    return getObjects().isEmpty();
  }

  /** When the fridge has no more room... */
  public boolean isFull() {
  }

  /** Answers whether the given object will fit. */
  public boolean canStore( Object o ) {
    boolean result = false;

    // Clients may not ask how much room remains in the fridge.
    if( o instanceof PhysicalObject ) {
      PhysicalObject po = (PhysicalObject)o;

      // How the fridge determines its remaining usable volume is a detail.
      // How a physical object determines whether it fits within a specified
      // volume is also a detail.
      result = po.isEnclosedBy( getUsableVolume() );
    }

     return result;
  }

  /** Doesn't expose how the fridge knows its warranty has expired. */
  public boolean isPastWarranty() {
    return getWarranty().before( new Date() );
  }

  /** Doesn't expose how objects are stored in the fridge. */
  public synchronized void store( Object o ) {
    validateExpiration( o );

    // Can the object fit?
    if( canStore( o ) ) {
      getObjects().add( o );
    }
    else {
      throw FridgeFullException( o );
    }
  }

  /** Doesn't expose how objects are removed from the fridge. */
  public synchronized void remove( Object o ) {
    if( !getObjects().contains( o ) ) {
      throw new ObjectNotFoundException( o );
    }

    getObjects().remove( o );

    validateExpiration( o );
  }

  /** Lazily initialized list, an implementation detail. */
  private synchronized List getObjects() {
    if( this.list == null ) { this.list = new List(); }
    return this.list;
  }

  /** How object expiration is determined is also a detail. */
  private void validateExpiration( Object o ) {
    // Objects can answer whether they have gone past a given
    // expiration date. How each object "knows" it has expired
    // is a detail. The Fridge might use a scanner and
    // items might have embedded RFID chips. It's a detail hidden
    // by proper encapsulation.
    if( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
      throw new ExpiredObjectException( o );
    }
  }

  /** This creates a copy of the warranty for immutability purposes. */
  private void setWarranty( Date warranty ) {
    assert warranty != null;
    this.warranty = new Date( warranty.getTime() )
  }
}
163
Dima

Der Hauptgrund für Getter und Setter in Java ist sehr einfach:

  • Sie können in einer Schnittstelle nur Methoden angeben, keine Felder.

Wenn Sie also zulassen möchten, dass ein Feld über die Schnittstelle geleitet wird, benötigen Sie eine Lese- und eine Schreibmethode. Diese werden traditionell als getX und setX für das Feld x bezeichnet.

42
user1249

Von http://www.adam-bien.com/roller/abien/entry/encapsulation_violation_with_getters_and

JavaBean-Stil:

connection.setUser("dukie");
connection.setPwd("duke");
connection.initialize();

OO-Stil:

connection.connect("dukie","duke");

Nun, klar, ich bevorzuge den letzteren Ansatz; Implementierungsdetails werden nicht ausgeblutet, es ist einfacher und präziser, und alle erforderlichen Informationen sind im Methodenaufruf enthalten, sodass es einfacher ist, sie richtig zu machen. Ich bevorzuge es auch, private Mitglieder mithilfe von Parametern im Konstruktor festzulegen, wann immer dies möglich ist.

Ihre Frage ist, wann ein Getter/Setter gerechtfertigt ist? Möglicherweise, wenn ein Moduswechsel erforderlich ist oder Sie ein Objekt nach Informationen abfragen müssen.

myObject.GetStatus();
myObject.SomeCapabilitySwitch = true;

Als ich darüber nachdachte, als ich anfing, in C # zu programmieren, schrieb ich viel Code im oben dargestellten Javabeans-Stil. Aber als ich Erfahrung in der Sprache sammelte, begann ich, mehr Elemente im Konstruktor festzulegen und Methoden zu verwenden, die eher dem obigen Stil ähnelten OO).

20
Robert Harvey

Wann sind Getter und Setter gerechtfertigt?

Wenn das Verhalten "get" und "set" tatsächlich mit dem Verhalten in Ihrem Modell übereinstimmt, ist praktisch nie.

Jede andere Verwendung ist nur ein Betrug, da das Verhalten der Geschäftsdomäne nicht klar ist.

Bearbeiten

Diese Antwort könnte leichtfertig gewesen sein, also lassen Sie mich näher darauf eingehen. Die obigen Antworten sind größtenteils richtig, konzentrieren sich jedoch auf die Programmierparadigmen von OO Design und einige, die das Gesamtbild verfehlen. Nach meiner Erfahrung führt dies dazu, dass die Leute denken, dass das Vermeiden von Gettings und Setzern eine akademische Regel für ist OO Programmiersprachen (z. B. Leute denken, dass das Ersetzen von Gettern durch Eigenschaften großartig ist)

Tatsächlich ist es eine Folge des Designprozesses. Sie müssen sich nicht mit Schnittstellen, Kapselungen und Mustern befassen und darüber streiten, ob diese Paradigmen verletzt werden oder nicht und was gut ist oder nicht OO Programmierung. Der einzige Punkt, der Letztendlich ist es wichtig, dass Sie Ihre Domain nicht modellieren, wenn in Ihrer Domain nichts vorhanden ist, das so funktioniert, indem Sie sie einfügen.

Die Realität ist, dass es höchst unwahrscheinlich ist, dass ein System in Ihrem Domänenbereich Getter und Setter hat. Sie können nicht zu dem Mann oder der Frau gehen, die für die Gehaltsabrechnung verantwortlich sind, und einfach "Setzen Sie dieses Gehalt auf X" oder "Holen Sie mir dieses Gehalt" sagen. Ein solches Verhalten gibt es einfach nicht

Wenn Sie dies in Ihren Code einfügen, entwerfen Sie Ihr System nicht so, dass es dem Modell Ihrer Domain entspricht. Ja, das bricht Schnittstellen und Kapselung, aber das ist nicht der Punkt. Der Punkt ist, dass Sie etwas modellieren, das nicht existiert.

Darüber hinaus fehlt Ihnen wahrscheinlich ein wichtiger Schritt oder Prozess, da es wahrscheinlich einen Grund gibt, warum ich nicht einfach zur Gehaltsliste gehen und sagen kann, dass Sie dieses Gehalt auf X setzen.

Wenn Leute Getter und Setter verwenden, neigen sie dazu, die Regeln für diesen Prozess an die falsche Stelle zu schieben. Dies entfernt sich noch mehr von Ihrer Domain. Am Beispiel der realen Welt ist es wie ein Gehalt, wenn man annimmt, dass die zufällige Person, die hereinkam, die Erlaubnis hat, diese Werte zu erhalten, sonst würde sie nicht danach fragen. Das ist nicht nur nicht so, wie die Domain ist, es lügt tatsächlich darüber, wie die Domain ist.

14
Cormac Mulhall

Getter und Setter sind in der Regel eine schlechte Idee. Wenn ein Feld nicht logisch Teil der Schnittstelle ist und Sie es privat machen, ist das in Ordnung. Wenn es logisch Teil der Benutzeroberfläche ist und Sie es öffentlich machen, ist das in Ordnung. Wenn Sie es jedoch privat machen und sich dann umdrehen und es wieder effektiv öffentlich machen, indem Sie einen Getter und Setter bereitstellen, kehren Sie zu Ihrem Ausgangspunkt zurück, außer dass Ihr Code jetzt ausführlicher und verschleierter ist.

Offensichtlich gibt es Ausnahmen. In Java müssen Sie möglicherweise Schnittstellen verwenden. Die Standardbibliothek Java) stellt Abwärtskompatibilitätsanforderungen, die so extrem sind, dass sie die normalen Maße der Codequalität überwiegen. Es ist sogar möglich, dass Sie sich tatsächlich mit dem legendären, aber seltenen Fall befassen, in dem es gute Chancen gibt Ersetzen Sie später ein gespeichertes Feld durch eine On-the-Fly-Berechnung, ohne die Schnittstelle anderweitig zu beschädigen. Dies sind jedoch Ausnahmen. Getter und Setter sind ein Anti-Pattern, das einer besonderen Begründung bedarf.

11
rwallace

ob das Feld direkt oder über die Methode zugänglich ist, ist nicht wirklich wichtig.

Klasseninvarianten (nützliche) sind wichtig. Und um sie zu bewahren, müssen wir manchmal nicht in der Lage sein, etwas von außen zu ändern. Z.B. Wenn wir eine Klasse Quadrat mit getrennter Breite und Höhe haben, wird sie durch Ändern einer Klasse zu etwas anderem als Quadrat. Wir brauchen also die Methode changeSide. Wenn es Rectangle wäre, könnten wir Setter/öffentliches Feld haben. Aber Setter als würde testen, ob es größer als Null besser wäre.

Und in konkreten Sprachen (zB Java) gibt es Gründe, warum wir diese Methoden (Schnittstellen) benötigen. Ein weiterer Grund für die Methode ist die Kompatibilität (Quelle und Binär). Es ist also einfacher, sie hinzuzufügen, als zu überlegen, ob das öffentliche Feld ausreichen würde.

übrigens. Ich verwende gerne einfache unveränderliche Werteklassen mit öffentlichen Abschlussfeldern.

3
user470365

Möglicherweise möchten Sie Ihre Interna ändern, während die Schnittstellen gleich bleiben. Wenn Ihre Schnittstellen nicht variieren, werden sie nicht beschädigt. Sie können Ihre Interna weiterhin nach Ihren Wünschen ändern.

2
George Silva

Stellen Sie sich eine Size -Klasse vor, die Breite und Höhe einschließt. Ich kann Setter mithilfe des Konstruktors eliminieren, aber wie hilft mir das, ein Rechteck mit Size zu zeichnen? Breite und Höhe sind keine internen Daten für die Klasse. Es handelt sich um gemeinsam genutzte Daten, die den Verbrauchern von Size zur Verfügung stehen müssen.

Objekte bestehen aus Verhalten und Status - oder Attributen. Wenn es keinen exponierten Zustand gibt, sind nur Verhaltensweisen öffentlich.

Wie würden Sie ohne Status eine Sammlung von Objekten sortieren? Wie würden Sie nach einer bestimmten Instanz eines Objekts suchen? Wenn Sie nur Konstruktoren verwenden, was passiert, wenn Ihr Objekt eine lange Liste von Attributen enthält?

Kein Methodenparameter sollte jemals ohne Validierung verwendet werden. Deshalb ist es faul zu schreiben:

setMyField(int myField){
    this.myField = myField;
}

Wenn Sie es so schreiben, haben Sie sich zumindest darauf vorbereitet, den Setter für die Validierung zu verwenden. Es ist besser als ein öffentliches Feld - aber nur knapp. Aber zumindest haben Sie eine konsistente öffentliche Schnittstelle, über die Sie zurückkehren und Validierungsregeln eingeben können, ohne den Code Ihrer Kunden zu verletzen.

Getter, Setter, Eigenschaften, Mutatoren nennen sie so, wie Sie wollen, aber sie sind notwendig.

0
Dale

Mein Ansatz ist dies -

Wenn ich erwarte, später mit den Daten zu experimentieren, ist ein Getter/Setter gerechtfertigt. Wenn sich etwas ändert, schiebe ich die Daten oft in einen Getter/Setter.

Wenn es sich um eine POD-Struktur handelt, lasse ich die Steckplätze frei.

Auf einer abstrakteren Ebene lautet die Frage "Wer verwaltet die Daten?", Und das hängt vom Projekt ab.

0
Paul Nathan

Wenn Getter und Setter die Kapselung und die wahre OO verletzen, bin ich ernsthaft in Schwierigkeiten.

Ich hatte immer das Gefühl, dass ein Objekt das darstellt, was mir am besten in den Sinn kommt.

Ich habe gerade ein Programm geschrieben, das Mazes in Java generiert, und ich habe eine Klasse, die "Maze Squares" darstellt. Ich habe Daten in dieser Klasse, die Koordinaten, Wände und Boolesche Werte usw. darstellen.

Ich muss eine Möglichkeit haben, diese Daten zu ändern/zu manipulieren/darauf zuzugreifen! Was mache ich ohne Getter und Setter? Java verwendet keine Eigenschaften und das Setzen aller meiner Daten, die für diese Klasse lokal sind, auf public ist definitiv eine Verletzung von Kapselung und OO.

0

Wenn sich die Verwendung von Gettern und Setzern kompliziert anfühlt, liegt das Problem möglicherweise in der Sprache und nicht im Konzept.

Hier ist der Code aus dem zweiten Beispiel in Ruby:

class Fridge
  attr_accessor :cheese
end

def go_shopping fridge
  fridge.cheese += 5
end

Beachten Sie, dass es dem Beispiel first in Java sehr ähnlich sieht? Wenn Getter und Setter als erstklassige Bürger behandelt werden, sind sie keine lästige Pflicht, und die zusätzliche Flexibilität kann manchmal ein wahrer Segen sein. Beispielsweise könnten wir uns dafür entscheiden, einen Standardwert für Käse für einen neuen Kühlschrank zurückzugeben:

class Fridge
  attr_accessor :cheese

  def cheese
    @cheese || 0
  end
end

Natürlich wird es viele Variablen geben, die überhaupt nicht öffentlich zugänglich gemacht werden sollten. Das unnötige Offenlegen interner Variablen verschlechtert Ihren Code, aber Sie können Getter und Setter kaum dafür verantwortlich machen.

0
Tobias Cohen