it-swarm.com.de

Ist es eine schlechte Idee, Getter / Setter und / oder Eigenschaften überhaupt zu verwenden?

Ich bin verwirrt über Kommentare unter dieser Antwort: https://softwareengineering.stackexchange.com/a/358851/212639

Ein Benutzer argumentiert dort gegen die Verwendung von Gettern/Setzern und Eigenschaften. Er behauptet, dass die meisten Male ein Zeichen für ein schlechtes Design sind. Seine Kommentare gewinnen positive Stimmen.

Nun, ich bin ein Anfänger im Programmieren, aber um ehrlich zu sein, finde ich das Konzept der Eigenschaften sehr ansprechend. Warum sollte ein Array keine Größenänderung zulassen, indem es in seine Eigenschaft size oder length schreibt? Warum sollten wir nicht alle Elemente in einem Binärbaum erhalten, indem wir dessen Eigenschaft size lesen? Warum sollte ein Automodell seine Höchstgeschwindigkeit nicht als Eigenschaft offenlegen? Wenn wir ein Rollenspiel machen, warum sollte ein Monster seinen Angriff nicht als Getter entlarven lassen? Und warum sollte es seine aktuellen HP nicht als Getter aussetzen? Oder wenn wir eine Home-Design-Software erstellen, warum sollten wir dann nicht zulassen, dass eine Wand neu eingefärbt wird, indem wir in ihren color Setter schreiben?

Es klingt alles so natürlich, so offensichtlich für mich, dass ich niemals zu dem Schluss kommen könnte, dass die Verwendung von Eigenschaften oder Getter/Setter ein Warnzeichen sein könnte.

Ist die Verwendung von Eigenschaften und/oder Getter-Setzern normalerweise eine schlechte Idee und wenn ja, warum?

12
gaazkam

Das einfache Anzeigen von Feldern - ob als öffentliche Felder, Eigenschaften oder Zugriffsmethoden - kann ein Indikator für eine unzureichende Objektmodellierung sein. Das Ergebnis ist, dass wir ein Objekt nach verschiedenen Daten fragen und Entscheidungen über diese Daten treffen. Diese Entscheidungen werden also außerhalb des Objekts getroffen. Wenn dies wiederholt geschieht, sollte diese Entscheidung möglicherweise in der Verantwortung des Objekts liegen und durch eine Methode dieses Objekts bereitgestellt werden: Wir sollten dem Objekt mitteilen was zu tun ist, und es sollte herausfinden, wie man das alleine macht.

Das ist natürlich nicht immer angebracht. Wenn Sie dies übertreiben, erhalten Ihre Objekte eine wilde Sammlung vieler kleiner, nicht verwandter Methoden, die nur einmal verwendet werden. Vielleicht ist es besser, Ihr Verhalten von Ihren Daten zu trennen und einen prozeduraleren Ansatz zu wählen. Das wird manchmal als "anämisches Domänenmodell" bezeichnet. Ihre Objekte weisen dann nur ein sehr geringes Verhalten auf, legen jedoch ihren Zustand durch einen Mechanismus offen.

Wenn Sie Eigenschaften verfügbar machen, sollten Sie sowohl bei der Benennung als auch bei der verwendeten Technik konsistent sein. Wie das geht, hängt hauptsächlich von der Sprache ab. Stellen Sie entweder alle als öffentliche Felder oder alle über Eigenschaften oder alle über Zugriffsmethoden bereit. Z.B. Kombinieren Sie kein rectangle.x - Feld mit einer rectange.y() - Methode oder eine user.name() - Methode mit einem user.getEmail() - Getter.

Ein Problem bei Eigenschaften und insbesondere bei beschreibbaren Eigenschaften oder Setzern besteht darin, dass Sie die Kapselung Ihres Objekts schwächen können. Die Kapselung ist wichtig, da Sie isoliert über die Richtigkeit Ihres Objekts nachdenken können. Diese Modularisierung ermöglicht es Ihnen, komplexe Systeme einfacher zu verstehen. Wenn wir jedoch auf Felder eines Objekts zugreifen, anstatt Befehle auf hoher Ebene auszugeben, werden wir zunehmend an diese Klasse gekoppelt. Wenn beliebige Werte von außerhalb eines Objekts in ein Feld geschrieben werden können, wird es sehr schwierig, einen konsistenten Zustand für das Objekt aufrechtzuerhalten. Es liegt in der Verantwortung des Objekts, sich selbst konsistent zu halten, und das bedeutet, eingehende Daten zu validieren. Z.B. Für ein Array-Objekt darf die Länge nicht auf einen negativen Wert festgelegt werden. Diese Art der Validierung ist für ein öffentliches Feld unmöglich und für einen automatisch generierten Setter leicht zu vergessen. Bei einer Operation auf hoher Ebene wie array.resize() ist dies offensichtlicher.

Vorgeschlagene Suchbegriffe für weitere Informationen:

22
amon

Es ist kein von Natur aus schlechtes Design, aber wie jedes leistungsstarke Feature kann es missbraucht werden.

Der Sinn von Eigenschaften (implizite Getter- und Setter-Methoden) besteht darin, dass sie eine leistungsstarke syntaktische Abstraktion bieten, die es dem konsumierenden Code ermöglicht, sie logisch als Felder zu behandeln und gleichzeitig die Kontrolle über das Objekt zu behalten, das sie definiert.

Während dies oft in Bezug auf die Kapselung gedacht wird, hat es oft mehr mit dem A-Steuerungsstatus und B mit syntaktischer Bequemlichkeit für den Anrufer zu tun.

Um einen Kontext zu erhalten, müssen wir zuerst die Programmiersprache selbst betrachten. Während viele Sprachen "Eigenschaften" haben, variieren sowohl die Terminologie als auch die Funktionen dramatisch.

Die Programmiersprache C # hat eine sehr ausgefeilte Vorstellung von Eigenschaften sowohl auf semantischer als auch auf syntaktischer Ebene. Es erlaubt entweder dem Getter oder dem Setter, optional zu sein, und es erlaubt ihnen kritisch, unterschiedliche Sichtbarkeitsstufen zu haben. Dies macht einen großen Unterschied, wenn Sie eine feinkörnige API verfügbar machen möchten.

Stellen Sie sich eine Assembly vor, die eine Reihe von Datenstrukturen definiert, die von Natur aus zyklisch Diagramme sind. Hier ist ein einfaches Beispiel:

public sealed class Document
{
    public Document(IEnumerable<Word> words)
    {
        this.words = words.ToList();
        foreach (var Word in this.words)
        {
            Word.Document = this;
        }
    }

    private readonly IReadOnlyList<Word> words;
}

public sealed class Word
{
    public Document Document
    {
        get => this.document;
        internal set
        {
            this.document = value;
        }
    }

    private Document document;
}

Idealerweise wäre Word.Document überhaupt nicht veränderbar, aber ich kann das nicht in der Sprache ausdrücken. Ich hätte einen IDictionary<Word, Document> Erstellen können, der Word s Document s aber letztendlich wird es irgendwo Veränderbarkeit geben

Hier soll ein zyklischer Graph erstellt werden, sodass jede Funktion, die ein Word erhält, das Word über die Word.Document - Eigenschaft (Getter) abfragen kann, ob es Document enthält ). Wir möchten auch eine Mutation durch Verbraucher verhindern, nachdem Document erstellt wurde. Der einzige Zweck des Setters besteht darin, die Verbindung herzustellen. Es soll nur einmal geschrieben werden. Dies ist schwierig, da wir zuerst die Instanzen Word erstellen müssen. Durch die Verwendung der Fähigkeit von C #, dem Getter und Setter der gleichen Eigenschaft nterschiedliche Zugänglichkeitsstufen zuzuweisen, können wir die Veränderbarkeit der Word.Document - Eigenschaft kapseln innerhalb der enthaltenden Versammlung.

Code, der diese Klassen von einer anderen Assembly verwendet, sieht die Eigenschaft als schreibgeschützt an (da nur ein get und kein set vorhanden ist).

Dieses Beispiel führt mich zu meiner Lieblingssache über Eigenschaften. Sie können nur einen Getter haben. Unabhängig davon, ob Sie einfach einen berechneten Wert zurückgeben möchten oder nur die Fähigkeit zum Lesen, aber nicht zum Schreiben verfügbar machen möchten, ist die Verwendung von Eigenschaften sowohl für die Implementierung nd) intuitiv und einfach der konsumierende Code.

Betrachten Sie das triviale Beispiel von

class Rectangle
{
   public Rectangle(double width, double height) => (Width, Height) = (width, height);

   public double Area { get => Width * Height; }

   public double Width { get; private set; }

   public double Height { get; private set; }
}

Wie ich bereits sagte, haben einige Sprachen unterschiedliche Konzepte für eine Eigenschaft. JavaScript ist ein solches Beispiel. Die Möglichkeiten sind komplex. Es gibt eine Vielzahl von Möglichkeiten, wie eine Eigenschaft von einem Objekt definiert werden kann, und es gibt sehr unterschiedliche Möglichkeiten, wie die Sichtbarkeit gesteuert werden kann.

Im trivialen Fall wie in C # können mit JavaScript jedoch Eigenschaften so definiert werden, dass nur ein Getter verfügbar gemacht wird. Dies ist wunderbar zur Kontrolle von Mutationen.

Erwägen:

function createRectangle(width, height) {
   return {
     get width() {
       return width;
     },
     get height() {
       return height;
     }
   };
}

Wann sollten Eigenschaften vermieden werden? Vermeiden Sie im Allgemeinen Setter mit Logik. Selbst eine einfache Validierung wird an anderer Stelle oft besser durchgeführt.

Wenn Sie zu C # zurückkehren, ist es oft verlockend, Code wie z

public sealed class Person
{
    public Address Address
    {
        get => this.address;
        set
        {
            if (value is null)
            {
                throw new ArgumentException($"{nameof(Address)} must have a value");
            }
            this.address = value;
        }
    }

    private Address address;
}

public sealed class Address { }

Die Ausnahme ist wahrscheinlich eine Überraschung für die Verbraucher. Immerhin wird Person.Address Als veränderlich deklariert und null ist ein vollkommen gültiger Wert für einen Wert vom Typ Address. Um die Sache noch schlimmer zu machen, wird ein ArgumentException ausgelöst. Dem Verbraucher ist möglicherweise nicht bewusst, dass er eine Funktion aufruft, und daher trägt die Art der Ausnahme noch mehr zum überraschenden Verhalten bei. Ein anderer Ausnahmetyp wie InvalidOperationException wäre wahrscheinlich besser geeignet. Dies zeigt jedoch, wo Setter hässlich werden können. Der Verbraucher denkt anders über Dinge nach. Es gibt Zeiten, in denen diese Art von Design nützlich ist.

Stattdessen ist es jedoch besser, Address zu einem erforderlichen Konstruktorargument zu machen oder eine dedizierte Methode zum Festlegen zu erstellen, wenn der Schreibzugriff verfügbar gemacht und eine Eigenschaft nur mit einem Getter verfügbar gemacht werden muss.

Ich werde dieser Antwort mehr hinzufügen.

3
Aluan Haddad

Das reflexive Hinzufügen von Gettern und Setzern (dh ohne zu verwechseln mit dem reflektierten Ausführen gemäß der von Ihnen verknüpften Frage) ist ein Zeichen für Probleme. Wenn Sie Passthrough-Getter und -Setter für Felder haben, warum machen Sie die Felder dann nicht einfach öffentlich? Zum Beispiel:

class Foo
{
    private int _i;
    public int getData() { return _i; }
    public void setData(int i) { _i = i; };
}

Was Sie oben getan haben, ist nur, die Verwendung der Daten zu erschweren und das Lesen mit Code zu erschweren. Anstatt nur foo.i += 1 Auszuführen, zwingen Sie den Benutzer, foo.setData(foo.getData() + 1) auszuführen, was nur stumpf ist. Und zu welchem ​​Vorteil? Angeblich machen Sie Getter und Setter für die Kapselung und/oder Datenkontrolle, aber wenn Sie einen öffentlichen Passthrough-Getter UND Setter haben, brauchen Sie sowieso keinen von diesen. Einige Leute argumentieren, dass Sie später möglicherweise die Datensteuerung hinzufügen möchten, aber wie oft wird dies in der Praxis durchgeführt? Wenn Sie öffentliche Getter und Setter haben, arbeiten Sie bereits auf einer Abstraktionsebene, wo Sie das einfach nicht tun. Und wenn es nicht möglich ist, können Sie es einfach an diesem Punkt beheben.

Öffentliche Getter und Setter sind ein Zeichen für Frachtkult-Programmierung, bei der Sie nur rote Regeln befolgen, weil Sie glauben, dass sie Ihren Code verbessern, aber nicht genau darüber nachgedacht haben, ob sie dies tun oder nicht. Wenn Ihre Codebasis mit Gettern und Setzern gefüllt ist, überlegen Sie, warum Sie einfach nicht mit PODs arbeiten. Vielleicht ist dies nur eine bessere Möglichkeit, mit Daten für Ihre Domain zu arbeiten.

Die Kritik an der ursprünglichen Frage ist nicht, dass Getter oder Setter eine schlechte Idee sind, sondern dass das Hinzufügen von öffentlichen Getter und Setter für alle Bereiche grundsätzlich nichts Gutes bringt.

3

Um das Problem mit Gettern und Setzern zu verstehen, ist es meiner Meinung nach wichtig, einen Schritt zurückzutreten und über Abstraktion und das Konzept einer Schnittstelle zu sprechen. Ich meine nicht das Programmierschlüsselwort interface wie in Java, ich meine Schnittstelle im allgemeineren Sinne. Wenn Sie beispielsweise einen Mikrowellenherd haben, verfügt dieser über eine Benutzeroberfläche, mit der Sie ihn steuern können. Das Schnittstellendesign ist einer der grundlegendsten Aspekte des Systemdesigns. Es hängt direkt damit zusammen, wie sich Ihr System entwickeln wird.

Beginnen wir mit einem negativen Beispiel. Angenommen, ich möchte Informationen zu meinem Inventar speichern. Also erstelle ich eine Klasse: Inventory. Zuallererst sagen meine Anforderungen, dass ich wissen muss, wie viele verschiedene Artikel oder ein bestimmter Typ im Inventar vorhanden sind, damit diese Informationen in anderen Teilen des Programms verwendet werden können. OK, lass uns codieren:

public class Inventory {
    public final String sku;
    public int numberOfItems;
}

Großartig, jetzt kann ich Inventarobjekte erstellen, die im Programm freigegeben werden können. Aber bald stoße ich auf ein Problem. Wenn Artikel verkauft oder dem Inventar hinzugefügt werden, muss ich diese Objekte aktualisieren. Es könnte viele verschiedene Leute geben, die diese Dinge gleichzeitig kaufen oder verkaufen. Wenn zwei Teile des Programms gleichzeitig versuchen, die Nummer zu aktualisieren, werden Fehler angezeigt. Nehmen wir zum Beispiel an, wir haben 50 Artikel und 100 Artikel wurden ins Lager gebracht, aber gleichzeitig haben wir 25 verkauft. Also lesen zwei Teile den Wert und ändern ihn dann und schreiben ihn zurück. Wenn der Verkaufscode den Wert zuletzt festlegt, haben wir jetzt 25 Artikel im Objekt, in Wirklichkeit haben wir 125.

Also fügen wir einen Wrapper hinzu und synchronisieren:

public class Inventory {
    public final String sku;
    private int numberOfItems;

    public int getNumberOfItems() {
        synchronized (this) {
            return numberOfItems;
        }
    }

    synchronized public void setNumberOfItems(int number) {
        synchronized (this) {
            this.numberOfItems = number;
        }
    }
}

Großartig. Jetzt können zwei Threads nicht gleichzeitig lesen und schreiben. Aber wir haben das Problem nicht wirklich gelöst. Die beiden Teile können immer noch (nacheinander) vom Getter lesen und dann (nacheinander) die falschen neuen Werte zurückschreiben. Wir sind nicht wirklich weitergekommen.

Hier ist ein besserer Ansatz:

public class Inventory {
    private final String sku;
    private int numberOfItems;

    public int getSku() {
      return sku;
    }

    public int getNumberOfItems() {
        synchronized (this) {
            return numberOfItems;
        }
    }

    public void addItems(int number) {
        synchronized (this) {
            this.numberOfItems += number;
        }
    }
}

Jetzt haben wir etwas, das das Problem tatsächlich angeht. Es stellt sicher, dass die Änderungen am Inventar einzeln vorgenommen werden.

Dies ist ein sehr einfaches Beispiel und soll nicht zeigen, wie man 2017 guten Code schreibt. Um ein robustes Design zu erhalten, müssen Sie sich darauf konzentrieren, wie Ihre Objekte/Klassen ihre Steuerelemente für andere Teile definieren der Anwendung. Die Definition des inneren Zustands hat damit sehr wenig zu tun. Nehmen wir im obigen Beispiel an, das Unternehmen wächst und wird jetzt verteilt. Unser Inventar wird irgendwo auf einem Remote-Server gespeichert. Es wird nicht einmal ein Feld geben. Um die Getter/Setter-Schnittstelle beizubehalten, müssten Sie andere Hilfsklassen erstellen, die Ihre Beans verwalten und die Daten hin und her übertragen. So funktionieren prozedurale (z. B. COBOL) Programme. Daten werden herumgeschoben und es gibt verschiedene Programme, die diese Daten manipulieren. Diese Arten von Designs sind sehr spröde und kostenintensiv zu ändern, da Sie viele unterschiedliche Codeteile haben, die gemeinsam geändert werden müssen. Jeder Teil kann Dinge tun, die andere Teile des Systems beschädigen.

Beachten Sie, dass es im verbesserten Design immer noch Getter gibt. Getter sind definitiv weniger problematisch als Setter. Während ich persönlich das Präfix get für Dinge nicht mag, gibt es Zeiten, in denen es am besten ist, den Erwartungen zu entsprechen. Aber Sie können immer noch Probleme mit Gettern bekommen. Wenn Sie Objekte wie Datenbeutel für die Lieferung an die Teile des Codes mit Logik verwenden, ist dies im Wesentlichen dasselbe Problem.

Ich hoffe das hilft. Kommentare willkommen.

0
JimmyJames