Welcher Stil ist in Java besser (Instanzvariable vs. Rückgabewert)?

32

Es fällt mir oft schwer zu entscheiden, welche dieser beiden Methoden ich verwenden soll, wenn ich gemeinsame Daten für einige Methoden in meinen Klassen verwenden möchte. Was wäre eine bessere Wahl?

Mit dieser Option kann ich eine Instanzvariable erstellen, um zu vermeiden, dass zusätzliche Variablen deklariert werden müssen, und um auch die Definition von Methodenparametern zu vermeiden. Es ist jedoch möglicherweise nicht klar, wo diese Variablen instanziiert / geändert werden:

public class MyClass {
    private int var1;

    MyClass(){
        doSomething();
        doSomethingElse();
        doMoreStuff();
    }

    private void doSomething(){
        var1 = 2;
    }

    private void doSomethingElse(){
        int var2 = var1 + 1;
    }

    private void doMoreStuff(){
        int var3 = var1 - 1;
    }
}

Oder einfach nur lokale Variablen instanziieren und als Argumente übergeben?

public class MyClass {  
    MyClass(){
        int var1 = doSomething();
        doSomethingElse(var1);
        doMoreStuff(var1);
    }

    private int doSomething(){
        int var = 2;
        return var;
    }

    private void doSomethingElse(int var){
        int var2 = var + 1;
    }

    private void doMoreStuff(int var){
        int var3 = var - 1;
    }
}

Wenn die Antwort lautet, dass beide richtig sind, welche wird dann häufiger gesehen / verwendet? Auch wenn Sie zusätzliche Vor- / Nachteile für jede Option angeben können, wäre dies sehr wertvoll.

carlossierra
quelle
1
Reletad (Duplikat?): Programmers.stackexchange.com/questions/164347/…
Martin Ba
1
Ich glaube noch nicht, dass jemand darauf hingewiesen hat, dass das Platzieren von Zwischenergebnissen in Instanzvariablen die Parallelität erschweren kann, da für diese Variablen Konflikte zwischen Threads auftreten können.
Sdenham

Antworten:

107

Ich bin überrascht, dass dies noch nicht erwähnt wurde ...

Es hängt davon ab, ob var1es sich tatsächlich um einen Teil des Status Ihres Objekts handelt .

Sie gehen davon aus, dass beide Ansätze korrekt sind und dass es nur um Stil geht. Sie liegen falsch.

Hier geht es ausschließlich darum, wie man richtig modelliert.

In ähnlicher Weise gibt es privateInstanzmethoden, um den Status Ihres Objekts zu ändern . Wenn Ihre Methode dies nicht tut, sollte es so sein private static.

MetaFight
quelle
7
@mucaho: transienthat nichts damit zu tun, da transientes sich um einen dauerhaften Zustand handelt, wie in Teilen des Objekts, die gespeichert werden, wenn Sie so etwas wie das Objekt serialisieren. Beispielsweise ist der Sicherungsspeicher einer ArrayList von transiententscheidender Bedeutung für den Zustand der ArrayList, da Sie beim Serialisieren einer ArrayList nur den Teil des Sicherungsspeichers speichern möchten, der die tatsächlichen ArrayList-Elemente enthält, nicht den freien Speicherplatz am Ende reserviert für weitere Elementzusätze.
user2357112 unterstützt Monica
4
Zur Antwort hinzufügen - Wenn dies var1für ein paar Methoden benötigt wird, aber nicht zum Status von gehört MyClass, ist es möglicherweise an der Zeit, var1diese Methoden in eine andere Klasse einzufügen, die von verwendet werden soll MyClass.
Mike Partridge
5
@CandiedOrange Hier geht es um die korrekte Modellierung. Wenn wir "Teil des Objektzustands" sagen, sprechen wir nicht über die wörtlichen Teile im Code. Wir treten in eine Diskussion konzeptionelle Idee des Staates, was sollte den Zustand des Objekts sein , die Konzepte richtig zu modellieren. Die "Nutzungsdauer" ist dabei wahrscheinlich der größte entscheidende Faktor.
jpmc26
4
@CandiedOrange Next und Previous sind offensichtlich öffentliche Methoden. Wenn der Wert von var1 nur für eine Sequenz privater Methoden relevant ist, gehört er eindeutig nicht zum permanenten Zustand des Objekts und sollte daher als Argument übergeben werden.
Taemyr
2
@CandiedOrange Nach zwei Kommentaren hast du klargestellt, was du meinst. Ich sage, dass Sie in der ersten Antwort leicht haben könnten. Außerdem habe ich vergessen, dass es mehr als nur das Objekt gibt. Ich bin damit einverstanden, dass private Instanzmethoden manchmal einen Zweck haben. Ich konnte mich einfach nicht daran erinnern. EDIT: Ich habe nur gemerkt, dass Sie nicht die erste Person waren, die mir geantwortet hat. Mein Fehler. Ich war verwirrt, warum eine Antwort ein kurzer, ein Satz, keine Antwort war, während die nächste tatsächlich die Dinge erklärte.
Fund Monica Klage
17

Ich weiß nicht, was häufiger vorkommt, aber ich würde immer Letzteres tun. Es kommuniziert den Datenfluss und die Lebensdauer klarer und bläht nicht jede Instanz Ihrer Klasse mit einem Feld auf, dessen einzige relevante Lebensdauer bei der Initialisierung liegt. Ich würde sagen, dass Ersteres nur verwirrend ist und die Codeüberprüfung erheblich erschwert, da ich die Möglichkeit in Betracht ziehen muss, dass eine Methode möglicherweise geändert wird var1.

Derek Elkins
quelle
7

Sie sollten den Umfang Ihrer Variablen so weit wie möglich (und sinnvoll) reduzieren . Nicht nur in Methoden, sondern allgemein.

Für Ihre Frage bedeutet dies, dass es davon abhängt, ob die Variable Teil des Objektstatus ist. Wenn ja, ist es in Ordnung, es in diesem Bereich zu verwenden, dh das gesamte Objekt. In diesem Fall wählen Sie die erste Option. Wenn nicht, wählen Sie die zweite Option, da sie die Sichtbarkeit von Variablen und damit die Gesamtkomplexität verringert.

Andy
quelle
5

Welcher Stil ist in Java besser (Instanzvariable vs. Rückgabewert)?

Es gibt noch einen anderen Stil - benutze einen Kontext / Zustand.

public static class MyClass {
    // Hold my state from one call to the next.
    public static final class State {
        int var1;
    }

    MyClass() {
        State state = new State();
        doSomething(state);
        doSomethingElse(state);
        doMoreStuff(state);
    }

    private void doSomething(State state) {
        state.var1 = 2;
    }

    private void doSomethingElse(State state) {
        int var2 = state.var1 + 1;
    }

    private void doMoreStuff(State state) {
        int var3 = state.var1 - 1;
    }
}

Dieser Ansatz bietet eine Reihe von Vorteilen. Die Zustandsobjekte können sich beispielsweise unabhängig vom Objekt ändern, was viel Spielraum für die Zukunft bietet.

Dies ist ein Muster, das auch in einem verteilten / Server-System gut funktioniert, in dem einige Details über Aufrufe hinweg beibehalten werden müssen. Sie können Benutzerdetails, Datenbankverbindungen usw. im stateObjekt speichern .

OldCurmudgeon
quelle
3

Es geht um Nebenwirkungen.

Die Frage, ob var1ein Teil des Staates ist, verfehlt den Punkt dieser Frage. Sicher, wenn var1es bestehen bleiben muss, muss es eine Instanz sein. Beide Ansätze können angewendet werden, um zu funktionieren, ob Persistenz erforderlich ist oder nicht.

Der Nebenwirkungsansatz

Einige Instanzvariablen werden nur zur Kommunikation zwischen privaten Methoden von Aufruf zu Aufruf verwendet. Diese Art von Instanzvariable kann aus der Existenz heraus überarbeitet werden, muss es aber nicht sein. Manchmal sind die Dinge mit ihnen klarer. Dies ist jedoch nicht ohne Risiko.

Sie lassen eine Variable aus ihrem Bereich heraus, da sie in zwei verschiedenen privaten Bereichen verwendet wird. Nicht, weil es in dem Bereich benötigt wird, in dem Sie es platzieren. Das kann verwirrend sein. Die "Globalen sind böse!" Grad der Verwirrung. Das kann funktionieren, ist aber nicht gut skalierbar. Es funktioniert nur im Kleinen. Keine großen Gegenstände. Keine langen Vererbungsketten. Verursacht keinen Jojo- Effekt.

Der funktionale Ansatz

Nun, auch wenn var1nichts bestehen bleiben muss , müssen Sie verwenden, wenn für jeden Übergangswert, den er annehmen könnte, bevor er den Zustand erreicht, den Sie zwischen öffentlichen Aufrufen beibehalten möchten. Das heißt, Sie können eine var1Instanz immer noch mit nur funktionaleren Methoden festlegen .

So Teil des Staates oder nicht, können Sie noch einen der beiden Ansätze verwenden.

In diesen Beispielen ist 'var1' so gekapselt, dass nichts anderes als Ihr Debugger weiß, dass es existiert. Ich vermute, Sie haben das absichtlich getan, weil Sie uns nicht voreingenommen machen wollen. Zum Glück ist mir egal welche.

Das Risiko von Nebenwirkungen

Das heißt, ich weiß, wo Ihre Frage herkommt. Ich habe unter miserablen gearbeitet yo yo ‚ing Erbe , das mutiert eine Instanzvariable auf mehreren Ebenen in mehreren Methoden und squirrelly gegangenes versuchen , ihm zu folgen. Das ist das Risiko.

Dies ist der Schmerz, der mich zu einem funktionaleren Ansatz treibt. Eine Methode kann ihre Abhängigkeiten dokumentieren und in ihrer Signatur ausgeben. Dies ist ein kraftvoller, klarer Ansatz. Sie können damit auch ändern, was Sie an der privaten Methode übergeben, sodass diese innerhalb der Klasse wiederverwendbarer ist.

Die Vorteile von Nebenwirkungen

Es ist auch einschränkend. Reine Funktionen haben keine Nebenwirkungen. Das kann eine gute Sache sein, ist aber nicht objektorientiert. Ein großer Teil der Objektorientierung ist die Fähigkeit, sich auf einen Kontext außerhalb der Methode zu beziehen. Es ist die Stärke von OOP, dies zu tun, ohne dass die Globalen hier und da auslaufen. Ich bekomme die Flexibilität eines globalen, aber es ist schön in der Klasse enthalten. Ich kann eine Methode aufrufen und jede Instanzvariable auf einmal ändern, wenn ich möchte. Wenn ich das tue, bin ich verpflichtet, der Methode zumindest einen Namen zu geben, der klar macht, worum es geht, damit die Leute nicht überrascht werden, wenn das passiert. Kommentare können ebenfalls hilfreich sein. Manchmal werden diese Kommentare als "Beitragsbedingungen" formalisiert.

Der Nachteil funktionaler privater Methoden

Der funktionale Ansatz macht einige Abhängigkeiten deutlich. Sofern Sie nicht in einer reinen funktionalen Sprache arbeiten, können versteckte Abhängigkeiten nicht ausgeschlossen werden. Wenn Sie sich nur eine Methodensignatur ansehen, wissen Sie nicht, dass sie im Rest des Codes keinen Nebeneffekt vor Ihnen verbirgt. Das tust du einfach nicht.

Post Bedingungen

Wenn Sie und alle anderen Mitglieder des Teams die Nebenwirkungen (vor / nach den Bedingungen) zuverlässig in Kommentaren dokumentieren, ist der Nutzen aus dem funktionalen Ansatz viel geringer. Ja, ich weiß, träume weiter.

Fazit

Persönlich tendiere ich in beiden Fällen zu funktionalen privaten Methoden, wenn ich kann, aber ehrlich gesagt, hauptsächlich, weil diese vor / nach-bedingten Nebenwirkungskommentare keine Compilerfehler verursachen, wenn sie veraltet sind oder wenn Methoden nicht in der richtigen Reihenfolge aufgerufen werden. Wenn ich nicht wirklich die Flexibilität von Nebenwirkungen brauche, möchte ich lieber nur wissen, dass die Dinge funktionieren.

MagicWindow
quelle
1
Msgstr "Einige Instanzvariablen werden nur zur Kommunikation zwischen privaten Methoden von Aufruf zu Aufruf verwendet." Das ist ein Codegeruch. Wenn Instanzvariablen auf diese Weise verwendet werden, ist dies ein Zeichen dafür, dass die Klasse zu groß ist. Extrahieren Sie diese Variablen und Methoden in eine neue Klasse.
Kevin Cline
2
Das ergibt wirklich keinen Sinn. Während das OP den Code in das Funktionsparadigma schreiben könnte (und dies wäre im Allgemeinen eine gute Sache), ist dies offensichtlich nicht der Kontext der Frage. Der Versuch, dem OP mitzuteilen, dass er das Speichern des Objektzustands durch einen Paradigmenwechsel vermeiden kann, ist nicht wirklich relevant ...
jpmc26
@kevincline Das Beispiel des OP hat nur eine Instanzvariable und 3 Methoden. Im Wesentlichen wurde es bereits in eine neue Klasse extrahiert. Nicht, dass das Beispiel irgendetwas Nützliches tut. Die Klassengröße hat nichts damit zu tun, wie Ihre privaten Hilfsmethoden miteinander kommunizieren.
MagicWindow
@ jpmc26 OP hat bereits gezeigt, dass sie var1durch einen Paradigmenwechsel das Speichern als Zustandsvariable vermeiden können . Der Klassenbereich ist NICHT nur dort, wo der Status gespeichert ist. Es ist auch ein umschließender Bereich. Das bedeutet, dass es zwei mögliche Gründe gibt, eine Variable auf Klassenebene zu setzen. Sie können behaupten, dies nur für den umschließenden Bereich zu tun und nicht, dass der Staat böse ist, aber ich sage, es ist ein Kompromiss mit der Arität, die auch böse ist. Ich weiß das, weil ich Code pflegen musste, der dies tut. Einige waren gut gemacht. Einige waren ein Albtraum. Die Linie zwischen den beiden ist kein Zustand. Es ist die Lesbarkeit.
MagicWindow
Die Statusregel verbessert die Lesbarkeit: 1) Vermeiden Sie, dass Zwischenergebnisse von Operationen wie in Status 2 aussehen. Vermeiden Sie es, die Abhängigkeiten von Methoden zu verbergen. Wenn die Arität einer Methode hoch ist, repräsentiert sie entweder irreduzibel und wirklich die Komplexität dieser Methode oder der aktuelle Entwurf weist eine unnötige Komplexität auf.
Sdenham
1

Die erste Variante sieht für mich nicht intuitiv und möglicherweise gefährlich aus (stellen Sie sich aus irgendeinem Grund vor, jemand macht Ihre private Methode öffentlich).

Ich würde Ihre Variablen viel lieber bei der Klassenkonstruktion instanziieren oder als Argumente übergeben. Letzteres bietet Ihnen die Möglichkeit, funktionale Redewendungen zu verwenden und sich nicht auf den Zustand des enthaltenen Objekts zu verlassen.

Brian Agnew
quelle
0

Es gibt bereits Antworten zum Objektstatus und wann die zweite Methode bevorzugt wird. Ich möchte nur einen allgemeinen Anwendungsfall für das erste Muster hinzufügen.

Das erste Muster ist vollkommen gültig, wenn Ihre Klasse lediglich einen Algorithmus kapselt . Ein Anwendungsfall hierfür ist, dass der Algorithmus zu groß wäre, wenn Sie ihn auf eine einzige Methode schreiben würden. Sie unterteilen es also in kleinere Methoden, machen es zu einer Klasse und machen die Untermethoden privat.

Wenn Sie nun den gesamten Status des Algorithmus über Parameter übergeben, wird dies möglicherweise mühsam, sodass Sie die privaten Felder verwenden. Es stimmt auch mit der Regel im ersten Absatz überein, da es im Grunde genommen ein Zustand der Instanz ist. Sie müssen nur bedenken und richtig dokumentieren, dass der Algorithmus nicht wiedereintrittsfähig ist, wenn Sie dafür private Felder verwenden . Dies sollte die meiste Zeit kein Problem sein, kann Sie aber möglicherweise beißen.

Honza Brabec
quelle
0

Lassen Sie uns ein Beispiel ausprobieren, das etwas bewirkt. Verzeih mir, das ist Javascript, nicht Java. Der Punkt sollte der gleiche sein.

Besuchen Sie https://blockly-games.appspot.com/pond-duck?lang=de , klicken Sie auf die Registerkarte Javascript und fügen Sie Folgendes ein:

hunt = lock(-90,1), 
heading = -135;

while(true) {
  hunt()  
  heading += 2
  swim(heading,30)
}

function lock(direction, width) {
  var dir = direction
  var wid = width
  var dis = 10000

  //hunt
  return function() {
    //randomize() //Calling this here makes the state of dir meaningless
    scanLock()
    adjustWid()
    if (isSpotted()) {
      if (inRange()) {
        if (wid <= 4) {
          cannon(dir, dis)
        }
      }
    } else {
      if (!left()) {
        right()
      }
    }
  }

  function scanLock() {
    dis = scan(dir, wid);
  }

  function adjustWid() {
    if (inRange()) {
      if (wid > 1)
        wid /= 2;
    } else {
      if (wid < 16) {
        wid *= 2; 
      }
    }
  }

  function isSpotted() {
    return dis < 1000;
  }

  function left() {
    dir += wid;
    scanLock();
    return isSpotted();
  }

  function right() {
    dir -= wid*2;
    scanLock();
    return isSpotted()
  }

  function inRange() {
    return dis < 70;
  }

  function randomize() {
    dir = Math.random() * 360
  }
}

Sie sollten feststellen , dass dir, widund disnicht um eine Menge geführt zu werden . Sie sollten auch beachten, dass der Code in der Funktion, die lock()zurückgibt, einem Pseudocode ähnelt. Nun, es ist tatsächlicher Code. Trotzdem ist es sehr leicht zu lesen. Wir könnten Übergabe und Zuweisung hinzufügen, aber das würde Unordnung hinzufügen, die Sie niemals im Pseudocode sehen würden.

Wenn Sie argumentieren möchten, dass es in Ordnung ist, das Zuweisen und Übergeben nicht durchzuführen, da diese drei Variablen einen beständigen Status aufweisen, sollten Sie ein Redesign in Betracht ziehen, dirbei dem jeder Schleife ein zufälliger Wert zugewiesen wird. Nicht beständig jetzt, oder?

Sicher, jetzt könnten wir den dirUmfang verringern, aber nicht ohne gezwungen zu sein, unseren Pseudo-ähnlichen Code mit Übergabe und Einstellung zu überfrachten.

Nein, der Status ist nicht der Grund, warum Sie sich entscheiden, Nebenwirkungen zu verwenden oder nicht zu verwenden, anstatt zu bestehen und zurückzukehren. Weder bedeuten Nebenwirkungen allein, dass Ihr Code nicht lesbar ist. Sie profitieren nicht von reinem Funktionscode . Aber gut gemacht, mit guten Namen, können sie tatsächlich schön zu lesen sein.

Das soll nicht heißen, dass sie sich nicht in einen Spaghetti wie einen Albtraum verwandeln können. Aber was kann das nicht?

Fröhliche Entenjagd.

kandierte_orange
quelle
1
Innere / äußere Funktionen sind ein anderes Paradigma als das, was OP beschreibt. - Ich stimme zu, dass der Kompromiss zwischen lokalen Variablen in einer äußeren Funktion, die Argumente an die inneren Funktionen übergeben hat, mit privaten Variablen in einer Klasse, die Argumente an private Funktionen übergeben hat, vergleichbar ist. Wenn Sie diesen Vergleich durchführen, entspricht die Lebensdauer des Objekts jedoch einem einzelnen Durchlauf der Funktion, sodass die Variablen in der äußeren Funktion Teil des dauerhaften Zustands sind.
Taemyr
@Taemyr the OP beschreibt eine private Methode, die in einem öffentlichen Konstruktor aufgerufen wird. Der Zustand ist nicht wirklich 'beständig', wenn Sie jedes Mal darauf treten, wenn Sie eine Funktion eingeben. Manchmal wird state verwendet, da Shared-Place-Methoden schreiben und lesen können. Bedeutet nicht, dass es bestehen bleiben muss. Die Tatsache, dass es sowieso bestehen bleibt, ist nichts, worauf man sich verlassen muss.
candied_orange
1
Es ist hartnäckig, auch wenn Sie jedes Mal darauf treten, wenn Sie das Objekt entsorgen.
Taemyr
1
Gute Punkte, aber das Ausdrücken in JavaScript verschleiert die Diskussion wirklich. Wenn Sie die JS-Syntax entfernen, wird am Ende das
Kontextobjekt herumgereicht
1
@sdenham Ich argumentiere, dass es einen Unterschied zwischen den Attributen einer Klasse und vorübergehenden Zwischenergebnissen einer Operation gibt. Sie sind nicht gleichwertig. Aber eines kann im anderen aufbewahrt werden. Das muss sicher nicht sein. Die Frage ist, ob es jemals sein sollte. Ja, es gibt semantische und lebenslange Probleme. Es gibt auch Probleme mit der Arität. Sie glauben nicht, dass sie wichtig genug sind. Das ist gut. Zu sagen, dass die Verwendung der Klassenebene für etwas anderes als den Objektstatus eine inkorrekte Modellierung ist, besteht jedoch auf einer Möglichkeit der Modellierung. Ich habe professionellen Code beibehalten, der es anders gemacht hat.
candied_orange