Ist diese Codestruktur in irgendeiner Weise vorteilhaft?


8

Ich wurde kürzlich in ein Java-Webanwendungsprojekt hineingeworfen und bin auf eine Reihe von Klassen gestoßen, die diesem Format folgen:

public class MyThingy {
   private final int p1;
   private final String p2;
   

   public MyThingy (int p1, String p2, …) {
      this.p1 = p1;
      this.p2 = p2;
      
   }

   public static void doSomething(int p1, String p2, …) throws Throwable     {
      final MyThingy myThingy = new MyThingy(p1, p2, …);
      myThingy.execute();
   }

   private void execute() throws Throwable {
      //do stuff 
   }
}

Es scheint, dass dies mit dem folgenden Code erreicht werden könnte, der mir viel einfacher zu lesen scheint.

public class MyThingy {

   public static void doSomething (int p1, String p2, …) throws Throwable {
      //do stuff 
   }

}

Der einzig mögliche Vorteil, den ich auf die erste Weise sehen kann, ist, dass wenn Sie execute () in kleinere Teile aufteilen müssten, diese alle die Anfangsparameter gemeinsam nutzen könnten, ohne sie explizit weitergeben zu müssen. Dies kommt jedoch möglicherweise nur dem faulen Codierer zugute, da der Leser nur schwer erkennen kann, welche Methoden welche Parameter benötigen und wann die Werte möglicherweise geändert werden (ähnlich wie bei globalen Variablen).

Fehlt mir etwas? Threading, Leistung?

Edit: Ich hätte erwähnen sollen, obwohl der Konstruktor öffentlich ist, wird er nicht aufgerufen. Die einzige Verwendung ist wie folgt:

MyThingy.doSomething(p1, p2...);

Abgesehen davon, dass dies an sich für das Testen problematisch ist, sehe ich keinen Grund, die Logik von execute () nicht direkt in doSomething () zu setzen. Selbst wenn wir die statische Funktion loswerden würden, macht der Konstruktor für mich immer noch keinen Sinn. Ich denke, die Parameter sollten direkt an die Methode übergeben werden, die sie verwendet.


Ich nehme nicht an, dass der Unternehmensstandard etwas über die Vermeidung statischer Funktionen aussagt. Welche Art von automatisierten Tests wird alternativ mit diesem Code durchgeführt? Das Verspotten wird fast unmöglich, wenn statische Funktionen eingeführt werden.
KChaloux

3
Nur als Hinweis: Ist throws Throwableeine schlechte Praxis, sollte es zumindest throws Exceptionoder etwas Spezifischeres sein, wenn möglich. Und da schlechte Praktiken normalerweise zusammenkommen, würde ich sagen, dass diese Codevorlage nur eine weitere schlechte Praxis ist.
Scriptin

Die statische Methode ist nur ein Hilfsmittel, um new(...)+ execute()in einem Aufruf auszuführen .
Kasey Speakman

Sie könnten es so oder so drehen: Dies ist eine hässliche Lösung. Es wäre besser, die Objekterstellung vom Verbrauch zu trennen.
Thomas Junk

1
Beachten Sie auch, dass der Autor offensichtlich beabsichtigt hat, dass das konstruierte Objekt unveränderlich ist (Daten sind final). Die statische Methode ist die einzige API im Objekt, da alle Mitglieder privat sind. In nur dem Code, den Sie geteilt haben, sehe ich keine Vorteile gegenüber executeeiner statischen Methode, die p1, p2, ... nimmt
Kasey Speakman

Antworten:


2

Ich denke, Ihre Intuition zu diesem Thema ist richtig.

  • Was wir hier haben, ist eine einfache (in diesem Fall statische) Methode, deren Implementierung in eine Klasse "aufgeteilt" wurde.
  • Die Technik "Methode als Klasse" reduziert die Anzahl der Variablen, die explizit weitergegeben werden müssen, und verdeckt gleichzeitig den Datenfluss mithilfe von "globalen" Variablen.
  • Nichts davon ist tatsächlich im Geiste von OO, da die Instanz wegwerfbar ist und ihr Zustand vergänglich ist.
  • Dies ist jedoch ein häufiges Muster, insbesondere in einführenden Java-Büchern, und möglicherweise in den Köpfen von Java-Designern (die viele Möglichkeiten zur Deklaration von Klassen eingeführt haben, um diese Verwendung anscheinend zu unterstützen).

Also, solltest du es benutzen ?

  • Sicher manchmal.

In der Tat geht es nicht nur um reine "Methoden als Klassen". In normalen Klassen könnten Sie versucht sein, Felder zu erstellen, die keinen Status zwischen Aufrufen öffentlicher Methoden enthalten. Sie werden nur verwendet, um die Übergabe von Parametern zwischen privaten Methoden zu vermeiden. In der Vergangenheit habe ich versucht, dies um jeden Preis zu vermeiden, aber dann festgestellt, dass auch lange Parameterlisten scheiße sind.

Ich versuche, reine "Methodenklassen" zu vermeiden, da ich denke, dass sie mit viel mentalem Gepäck verbunden sind (so viel potenzielles Potenzial). (Ich lehne mich auch an Einwegfelder in regulären Klassen.) Aber wenn es eine Menge Variablen gibt, die weitergegeben werden müssen, kann sich der Gewinn an Code-Sauberkeit lohnen.

Ich versuche, Klassen zu verwenden, wenn es einen Sinn gibt, und tatsächlich ist es normalerweise leicht, sich einen vorzustellen. Vielleicht erweitern Sie die Klasse in Zukunft um zusätzliche Implementierungen. Möglicherweise kann die Instanz als Funktionsobjekt betrachtet werden, das Sie initialisieren und weitergeben möchten. Und dann ist die Klasse keine Pseudo-OO- "Methodenklasse" mehr.


1
Mir gefällt, was Sie hier im letzten Absatz vorschlagen. Wenn die Methode execute () öffentlich wäre, könnte ich das Objekt erstellen (und initialisieren) und es später von etwas ausführen lassen. Das könnte sinnvoll sein und würde auch die Testbarkeit verbessern. In diesem Fall ist tatsächlich die statische Funktion irrelevant.
Mishelle

Dies ist die Antwort, auf die diese Frage gewartet hat. +1
cbojar

6

Die zusätzliche Indirektion durch den statischen Methodenaufruf trennt die Bedenken, wie ein Objekt erstellt wird, von dem Code, der das Objekt verwendet. Was Sie hier haben, ist einer einfachen Factory-Methode sehr ähnlich (es gibt das Objekt nicht zurück, aber die Indirektion der Objekterstellung ist dieselbe).

Dies kann nützlich sein, wenn Sie den Typ des erstellten Objekts steuern müssen. In diesem einfachen Beispiel muss keine Entscheidung getroffen werden, aber es wäre einfach, diese Logik hinzuzufügen.

Für Anrufer bedeutet der statische Code: "Ich muss etwas mit diesem Status tun, weiß aber nicht, wie ich delegieren soll." Die statische Methode kann dann basierend auf dem bereitgestellten Objekt an die richtige Implementierung delegieren.

Möglicherweise wird beim Ausführen eines Komponententests ein Scheinobjekt verwendet. Möglicherweise könnte basierend auf den Parametern ein anderes Objekt ausgetauscht werden, das einen anderen Algorithmus implementiert, um alles zu tun, was es tun muss. Durch Lokalisieren dieses Codes an einem Ort kann die Entscheidung an einem Ort getroffen werden und nicht an beliebig vielen.


Angesichts des Wortlauts Ihrer Frage und der Tatsache, dass bei den statischen Methoden keine Konstruktionsentscheidung getroffen wird, habe ich das Gefühl, dass dies möglicherweise nur ein Fall von Überentwicklung ist. Der meiste Code muss nicht an eine Factory delegiert werden. Die Tatsache , dass Sie Code halten Begegnung dieses wie das macht nicht alle Entscheidungen treffen , wie ich eine statische Factory-Methode wie mir sagt , zu tun erwarten würde jemand lesen Sie die GoF Buch und lief Amok.


1
Wo sehen Sie eine Factory-Methode? Keines der Beispiele des OP gibt etwas zurück.
Robert Harvey

@ RobertHarvey: Es ist effektiv eine Wrapped Factory-Methode. Nur weil es nicht der Rufseite ausgesetzt ist, heißt das nicht, dass es keine Fabrik ist!
Leichtigkeitsrennen im Orbit

1
@ RobertHarvey Ich denke, das ist eine Frage der Wortwahl. Es verhält sich ähnlich wie eine Factory-Methode, gibt jedoch nichts zurück. Andererseits habe ich nie behauptet, dass es delegiert hat, nur dass es delegiert hat, was immer noch eine genaue Aussage ist.

Ich stimme @RobertHarvey zu. Ein Objekt, das vollständig innerhalb einer Methode erstellt und verwendet wird, ist ein Implementierungsdetail und kein Fabrikprodukt.
Cbojar

Interessant. Ich habe die Terminologie in meiner Antwort geändert, aber der Punkt, den ich gemacht habe, ist identisch.

0

Nun, wenn Ihr zweites Beispiel (das den vollständigen Code zeigt) so aussieht:

public static void doSomething(int p1, String p2, …) throws Throwable     {
      final MyThingy myThingy = new MyThingy(p1, p2, …);
      myThingy.execute();
   }

dann brauchen Sie noch den Konstruktor

public MyThingy (int p1, String p2, …) {
      this.p1 = p1;
      this.p2 = p2;
      
   }

was wiederum setzt

   private final int p1;
   private final String p2;

und jetzt bist du wieder da, wo du angefangen hast.

Sie können natürlich auch den Konstruktor eliminieren und stattdessen Getter und Setter verwenden

private final int p1;
private final String p2;

public void SetP1(int p1)
{
    this.p1 = p1;
}

public int GetP1()
{
    return p1;
}
// repeat for p2

... aber dies erschwert Ihren statischen Methodenaufruf, da Sie jetzt mehrere Codezeilen benötigen, um die Setter zuerst aufzurufen, bevor Sie Ihre statische Methode aufrufen, wobei eine einzelne Zeile, die den Konstruktoraufruf verwendet, ausgereicht hätte.

Dies funktioniert natürlich nur, wenn Ihre Mitgliedsvariablen dies nicht sind final. Daher benötigen Sie immer noch den Konstruktor.


Es ist nicht sinnvoll, einen öffentlichen Setter für eine finalVariable zu haben. Es kann nur einmal initialisiert werden.
Kasey Speakman

1
@KaseySpeakman: Ergo, du brauchst noch einen Konstruktor.
Robert Harvey

Ich bin nicht ganz sicher, ob diese Antwort auf die Frage spricht ...
cbojar

Darüber hinaus ist es ungültig (hat einen Kompilierungsfehler): ideone.com/0nDRgY --- Der Endwert muss nachweislich nirgendwo anders einstellbar sein. Ein Setter macht dies unmöglich und damit einen Kompilierungsfehler.

@MichaelT: Das habe ich schon in meiner Antwort gesagt (im letzten Absatz).
Robert Harvey

0

Normalerweise verfügt eine Klasse über viele Instanzmethoden. Sobald das Objekt erstellt wurde, kann die Implementierung von execute alle diese Instanzmethoden nutzen. Wenn Sie sich weigern, ein Objekt zu erstellen, sind alle diese Instanzmethoden nutzlos.


Ich denke, das OP sagt, dass es daneben keine anderen Instanzmethoden gibt execute. Also fragen, warum nicht das Ganze inline.
Cbojar

Ja, das ist genau meine Frage!
Mishelle
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.