Methodenflags als Argumente oder als Mitgliedsvariablen?

8

Ich denke der Titel "Methodenflags als Argumente oder als Mitgliedsvariablen?" mag suboptimal sein, aber da mir eine bessere Terminologie fehlt, geht es weiter:

Ich versuche gerade, mich mit dem Problem auseinanderzusetzen, ob Flags für eine bestimmte ( private ) Klassenmethode als Funktionsargumente oder über eine Mitgliedsvariable übergeben werden sollen und / oder ob es ein Muster oder einen Namen gibt, der diesen Aspekt und / oder abdeckt ob dies auf andere Designprobleme hindeutet.

Zum Beispiel (Sprache könnte C ++, Java, C # sein, spielt meiner Meinung nach keine Rolle):

class Thingamajig {
  private ResultType DoInternalStuff(FlagType calcSelect) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (calcSelect == typeA) {
        ...
      } else if (calcSelect == typeX) {
        ...
      } else if ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(FlagType calcSelect) {
    ...
    DoInternalStuff(calcSelect);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(typeA);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(typeX);
    ... some more code ... further process x ...
    return x;
  }
}

Was wir oben sehen, ist, dass die Methode InternalStuffInvokerein Argument verwendet, das in dieser Funktion überhaupt nicht verwendet wird, sondern nur an die andere private Methode weitergeleitet wird DoInternalStuff. (Wo DoInternalStuffwird privat an anderen Orten in dieser Klasse verwendet, z. B. in der DoThatStuff(öffentlichen) Methode.)

Eine alternative Lösung wäre das Hinzufügen einer Mitgliedsvariablen, die diese Informationen enthält:

class Thingamajig {
  private ResultType DoInternalStuff() {
    ResultType res;
    for (... some loop condition ...) {
      ...
      if (m_calcSelect == typeA) {
        ...
      } ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker() {
    ...
    DoInternalStuff();
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    m_calcSelect = typeA;
    InternalStuffInvoker();
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    m_calcSelect = typeX;
    ResultType x = DoInternalStuff();
    ... some more code ... further process x ...
    return x;
  }
}

Insbesondere bei tiefen Aufrufketten, bei denen das Selektor-Flag für die innere Methode außerhalb ausgewählt ist, kann die Verwendung einer Elementvariablen die Zwischenfunktionen sauberer machen, da sie keinen Pass-Through-Parameter enthalten müssen.

Andererseits repräsentiert diese Mitgliedsvariable keinen Objektstatus (da sie weder festgelegt noch außerhalb verfügbar ist), sondern ist wirklich ein verstecktes zusätzliches Argument für die "innere" private Methode.

Was sind die Vor- und Nachteile jedes Ansatzes?

Martin Ba
quelle
1
Bedenken Sie, dass Sie wahrscheinlich eine Reihe von Funktionen haben, die wesentlich mehr als eine Sache ausführen, wenn Sie Flags weitergeben müssen. Sie könnten in Betracht ziehen, den Ablauf der Steuerung zu korrigieren, damit Sie sie nicht mit Flags verschleiern müssen.
CHao
Ich möchte auch erwähnen, dass "DoInternalStuff" und "InternalStuff Invoker" wirklich beschissene Namen sind. :) Ja, ich weiß, es ist nur ein Beispiel, aber es ist irgendwie bedrohlich. Wenn Ihr echter Code so vage ist wie "internes Zeug", ist das ein größeres Problem. Sortieren Sie das aus, und es ist wahrscheinlicher, dass sich eine Möglichkeit zur Vereinfachung des Kontrollflusses zeigt.
CHao
1
Ich stimme @cHao zu - wo ich in der Vergangenheit solche Situationen hatte, war das häufig übergebene Optionsflag ein Code-Geruch, dass das Prinzip der Einzelverantwortung verletzt wurde.
Bevan

Antworten:

15

Andererseits repräsentiert diese Mitgliedsvariable keinen Objektstatus (da sie weder festgelegt noch außerhalb verfügbar ist), sondern ist wirklich ein verstecktes zusätzliches Argument für die "innere" private Methode.

Das verstärkt mein Bauchgefühl beim Lesen Ihrer Frage: Versuchen Sie, diese Daten so lokal wie möglich zu halten, dh fügen Sie sie nicht dem Objektstatus hinzu . Dies reduziert den Statusbereich Ihrer Klasse und erleichtert das Verstehen, Testen und Verwalten Ihres Codes. Ganz zu schweigen davon, dass das Vermeiden eines gemeinsamen Status Ihre Klasse auch threadsicherer macht - dies kann für Sie im Moment ein Problem sein oder auch nicht, aber es kann in Zukunft einen großen Unterschied machen.

Wenn Sie solche Flaggen übermäßig herumreichen müssen, kann dies natürlich zu einem Ärgernis werden. In diesem Fall können Sie in Betracht ziehen

  • Erstellen von Parameterobjekten, um verwandte Parameter zu einem einzigen Objekt zusammenzufassen, und / oder
  • Einkapseln dieses Zustands und der zugehörigen Logik in eine separate Familie von Strategien . Die Realisierbarkeit hängt von der Sprache ab: In Java müssen Sie für jede Strategie eine eigene Schnittstelle und eine (möglicherweise anonyme) Implementierungsklasse definieren, während Sie in C ++ oder C # Funktionszeiger, Delegaten oder Lambdas verwenden können, was den Code vereinfacht wesentlich.
Péter Török
quelle
Vielen Dank für die Erwähnung des Strategie- / Lambda-Ansatzes. Ich muss diese Lösung behalten, wenn die Dinge auf den Kopf gestellt werden müssen :-)
Martin Ba
6

Ich würde definitiv für die Option "Argument" stimmen - meine Gründe:

  1. Synchronisation - Was ist, wenn die Methode von mehreren Threads gleichzeitig aufgerufen wird? Wenn Sie die Flags als Argument übergeben, müssen Sie den Zugriff auf diese Flags nicht synchronisieren.

  2. Testbarkeit - Stellen Sie sich vor, Sie schreiben einen Komponententest für Ihre Klasse. Wie testen Sie den internen Status des Mitglieds? Was passiert, wenn Ihr Code eine Ausnahme auslöst und das Mitglied in einem unerwarteten Zustand endet?

  3. Trennung von Bedenken - Durch Hinzufügen der Flags zu Ihren Methodenargumenten wird klar, dass keine andere Methode diese verwenden soll. Sie werden es nicht versehentlich in einer anderen Methode verwenden.

Sulthan
quelle
3

Die erste Alternative scheint mir in Ordnung zu sein. Eine Funktion wird mit einem Parameter aufgerufen und führt etwas aus, das von diesem Parameter abhängt . Selbst wenn Sie den Parameter nur an eine andere Funktion weiterleiten, erwarten Sie, dass das Ergebnis irgendwie vom Parameter abhängt.

Die zweite Alternative besteht darin, eine globale Variable nur innerhalb des Klassenbereichs anstelle des Anwendungsbereichs zu verwenden. Aber es ist das gleiche Problem ... Sie müssen den gesamten Klassencode sorgfältig lesen, um festzustellen, wer und wann diese Werte liest und schreibt.

Daher gewinnt die erste Alternative.

Wenn Sie in der ersten Alternative zu viele Parameter (lesen Sie: zwei oder mehr) verwenden würden, die alle zusammen an die nächste Funktion übergeben werden, können Sie sie in ein Objekt einfügen (machen Sie es zu einer inneren Klasse, wenn es für nicht relevant ist den Rest der Anwendung) und Verwendung dieses Objekts als Parameter.

Viliam Búr
quelle
1

Ich möchte "weder" sagen.

Denken Sie zunächst daran, dass Sie bereits wissen, was Sie tun werden, wenn Sie anrufen DoInternalStuff. Die Flagge sagt so viel. So , jetzt, statt nur voran gehen und tun es, Sie furzen um eine Funktion aufrufen , dass nun entscheiden muss , was zu tun ist .

Wenn Sie der Funktion mitteilen möchten, was zu tun ist, teilen Sie ihr mit, was zu tun ist . Sie können eine tatsächliche Funktion, die Sie für jede Iteration ausführen möchten, als C # -Delegat übergeben (oder als C ++ - Funktionsobjekt oder als Java-ausführbare Datei oder dergleichen).

In C #:

class Thingamajig {
  private delegate WhateverType Behavior(ArgsType args);

  private ResultType DoInternalStuff(Behavior behavior) {
    ResultType res;
    for (... some loop condition ...) {
      ...
      var subResult = behavior(someArgs);
      ...
    }
    ...
    return res;
  }

  private void InteralStuffInvoker(Behavior behavior) {
    ...
    DoInternalStuff(behavior);
    ...
  }

  public void DoThisStuff() {
    ... some code ...
    InternalStuffInvoker(doThisStuffPerIteration);
    ... some more code ...
  }

  public ResultType DoThatStuff() {
    ... some code ...
    ResultType x = DoInternalStuff(doThatStuffPerIteration);
    ... some more code ... further process x ...
    return x;
  }
}

Sie enden immer noch mit dem Streit, der überall herumgeht, aber Sie werden den größten Teil des Wenn / Sonst-Mistes los DoInternalStuff. Sie können den Delegaten auch als Feld speichern, wenn Sie möchten.

Ob Sie das ToDo-Ding weitergeben oder aufbewahren möchten ... Wenn Sie eines auswählen müssen, geben Sie es weiter. Wenn Sie es aufbewahren, werden Sie wahrscheinlich später in den Arsch gebissen, da Sie die Fadensicherheit so gut wie vernachlässigt haben. Bedenken Sie, dass Sie, wenn Sie beabsichtigen, etwas mit Threads zu tun, jetzt für die gesamte Dauer der Schleife sperren müssen, oder jemand kann die Aufgaben direkt unter Ihnen wechseln. Wenn Sie es bestehen, ist es weniger wahrscheinlich, dass es in den Papierkorb fällt ... und wenn Sie immer noch sperren müssen oder so, können Sie dies für einen Teil einer Iteration anstelle der gesamten Schleife tun.

Ehrlich gesagt ist es gut, dass es nervt. Es gibt wahrscheinlich einen viel einfacheren Weg, um das zu tun, was Sie tun möchten, worüber ich Sie nicht wirklich beraten konnte, da der Code offensichtlich nicht der echte ist. Die beste Antwort variiert je nach Fall.

cHao
quelle