Generierung von Builder-Mustercode in IntelliJ

82

Gibt es eine Möglichkeit, das Schreiben von Builder-Mustern in IntelliJ zu automatisieren?

Zum Beispiel angesichts dieser einfachen Klasse:

class Film {
   private String title;
   private int length;

   public void setTitle(String title) {
       this.title = title;
   }

   public String getTitle() {
       return this.title;
   }

   public void setLength(int length) {
       this.length = length;
   }

   public int getLength() {
       return this.length;
   }
}

Gibt es eine Möglichkeit, wie ich die IDE dazu bringen könnte, dies oder ähnliches zu generieren:

public class FilmBuilder {

    Film film;

    public FilmBuilder() {
        film = new Film();
    }

    public FilmBuilder withTitle(String title) {
        film.setTitle(title);
        return this;
    }

    public FilmBuilder withLength(int length) {
        film.setLength(length);
        return this;
    }

    public Film build() {
        return film;
    }
}
Martyn
quelle

Antworten:

106

Verwenden Sie den Konstruktor durch Builder- Refactoring ersetzen .

Um diese Funktion zu verwenden, klicken Sie auf die Signatur des Konstruktors in Ihrem Code, klicken Sie mit der rechten Maustaste und wählen Sie das Menü "Refactor" aus. Klicken Sie dann auf "Konstruktor durch Builder ersetzen ...", um das Dialogfeld zum Generieren des Codes aufzurufen.

CrazyCoder
quelle
12
Gibt es eine Möglichkeit, "setX ()" in "withX ()" zu ändern, Serge?
ae6rt
5
Oder können Sie setX () in x () ändern?
Kurru
Nett! Nie neu, dass es gab. Vielen Dank
Wikingersteve
16
Sie können den Namen der Setter-Methoden ändern, indem Sie auf das Zahnrad in der oberen rechten Ecke des Fensters "Builder erstellen" klicken und "Setter-Präfix umbenennen" auswählen. Dann können Sie es in "mit" ändern oder einfach das Präfix vollständig entfernen.
Chris B
3
Ist es möglich, dass IntelliJ die Builder-Klasse automatisch als innere Klasse erstellt?
Kit
23

Ich fand die in IntelliJ integrierte Generierung von Builder-Mustern aus mehreren Gründen etwas umständlich:

  1. Es muss ein vorhandener Konstruktor als Referenz verwendet werden.
  2. Es ist nicht schnell über das Menü "Generieren" ( command+Nunter OS X) zugänglich .
  3. Es werden nur externe Builder-Klassen generiert. Wie andere bereits erwähnt haben, werden bei der Implementierung des Builder-Musters häufig statische innere Klassen verwendet.

Das InnerBuilder-Plugin behebt alle diese Mängel und erfordert keine Einrichtung oder Konfiguration. Hier ist ein Beispiel-Builder, der vom Plugin generiert wurde:

public class Person {
    private String firstName;
    private String lastName;
    private int age;

    private Person(Builder builder) {
        firstName = builder.firstName;
        lastName = builder.lastName;
        age = builder.age;
    }

    public static final class Builder {
        private String firstName;
        private String lastName;
        private int age;

        public Builder() {
        }

        public Builder firstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder lastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Person build() {
            return new Person(this);
        }
    }
}
Mansoor Siddiqui
quelle
3
In meiner Antwort finden Sie eine integrierte Lösung für alle genannten Probleme.
jFrenetic
1
Sie machen einige gute Punkte @jFrenetic, aber ich finde den eingebauten Ansatz letztendlich immer noch zu umständlich, selbst mit diesen Problemumgehungen. Mit dem Plugin, es ist buchstäblich nur alt+shift+B, enterund BAM, haben Sie Ihre Erbauer. : D
Mansoor Siddiqui
1
OMG das ist ein wahr gewordener Traum. Der eingebaute Builder hat es nicht für mich getan - ich bevorzuge es, den Builder als innere Klasse des PoJo zu haben. Danke ein schöner Tipp!
Somaiah Kumbera
16

So überwinden Sie die von Mansoor Siddiqui genannten Mängel :

1) Es muss ein vorhandener Konstruktor als Referenz verwendet werden.

Welches ist sehr einfach zu generieren. Drücken Sie unter Windows einfach Alt+ Ins, um das Menü Generieren aufzurufen, und wählen Sie Constructor.

2) Der Zugriff über das Menü "Generieren" (Befehl + N unter OS X) ist nicht schnell.

Gehen Sie einfach zu Settings -> Keymap, suchen Sie danach Builderund weisen Sie ihm eine Verknüpfung Ihrer Wahl zu (wenn Sie diese Funktionalität sehr oft verwenden, was selten der Fall ist). Sie können zum Beispiel Alt+ zuweisen B.

Eine andere Alternative ist Ctrl+ Shift+ A(Aktion suchen). Beginnen Sie mit der Eingabe Builderund Sie erhalten sofort Zugriff auf den Befehl:

Dialogfeld "Aktion suchen"

Sie können diese Verknüpfung verwenden, um schnell auf eine IntelliJ IDEA-Funktion zuzugreifen (dies ist sehr hilfreich, wenn Sie sich nicht genau erinnern, wie der Befehl aufgerufen wird und wo er zu finden ist).

3) Es werden nur externe Builder-Klassen generiert. Wie andere bereits erwähnt haben, werden bei der Implementierung des Builder-Musters häufig statische innere Klassen verwendet .

Ich bevorzuge meine Erbauer auch als statische innere Klassen. Leider gibt es keinen einfachen Weg, aber es ist immer noch machbar. Sie müssen nur die verschachtelte innere Klasse selbst definieren (leer lassen). Wenn Sie den Replace Constructor with BuilderDialog aufrufen , wählen Sie die Use existingOption und wählen Sie Ihre innere Klasse aus. Klappt wunderbar! Es wäre jedoch einfacher gewesen, diese Option konfigurierbar zu machen.

jFrenetic
quelle
11

Wenn Sie sich fragen, ob dies zum Erstellen einer Klasse mit einer inneren Builder-Klasse verwendet werden kann, wie von Joshua Block beschrieben, müssen Sie zuerst eine leere innere Klasse definieren, dann "Vorhandene verwenden" aktivieren und nach Ihrer neu erstellten (inneren Klasse suchen) ) und drücke "Refactor".

PS! Der Cursor muss sich im Konstruktor befinden (vorab geschrieben), damit die Refactoring-Funktion "Konstruktor durch Builder ersetzen" verwendet werden kann.

PålOliver
quelle
Es ist interessant, dass diese Plugins einen wichtigen Teil des Builder-Musters vergessen: Sie sollten weiterhin die erforderlichen Parameter im Konstruktor definieren. Andernfalls sind Sie vom statischen Abfangen fehlender Parameterfehler zum Abfangen nur zur Laufzeit übergegangen.
EJ Campbell
@EJCampbell Dies würde einen der Zwecke der Verwendung eines Builders zunichte machen, nämlich die Lesbarkeit des Codes durch die Bereitstellung einer Möglichkeit, den Parametern Namen zuzuweisen, selbst wenn diese erforderlich sind. Dies ist hilfreich, wenn ein Konstruktor eine relativ große Anzahl von Parametern verwendet. Es wäre nicht notwendig, wenn Java uns erlauben würde, tatsächliche Parameter im Aufruf zu benennen, wie es C # und Swift tun.
Ajb
5

Der intelligente Weg dahin ist meiner Meinung nach verworren. Es gibt zwei Plugins (ich bevorzuge dieses: https://plugins.jetbrains.com/plugin/7354 ), die den Zweck viel besser erfüllen.

Zum Beispiel bevorzuge ich die Builder-Klasse als innere Klasse des PoJo. Um dies mit IntelliJ zu erreichen, benötigen Sie einige zusätzliche Striche.

Ein weiteres Plus für das Plugin ist der Speicherort der Funktionalität (im Generate...Kontextmenü).

nucatus
quelle
4

Lombok ist der einfachste Weg, es zu tun! (Es hat ein Intellij-Plugin für die Syntaxunterstützung https://plugins.jetbrains.com/plugin/6317-lombok-plugin )

Fügen Sie einfach eine @BuilderAnmerkung hinzu, und hinter den Wänden wird eine Builder-Implementierung innerhalb des Objekts hinzugefügt.

Mit Lombok:

import lombok.Builder;
import lombok.Singular;
import java.util.Set;

@Builder
public class BuilderExample {
  @Builder.Default private long created = System.currentTimeMillis();
  private String name;
  private int age;
  @Singular private Set<String> occupations;
}

Ohne Lombok:

import java.util.Set;

public class BuilderExample {
  private long created;
  private String name;
  private int age;
  private Set<String> occupations;
  
  BuilderExample(String name, int age, Set<String> occupations) {
    this.name = name;
    this.age = age;
    this.occupations = occupations;
  }
  
  private static long $default$created() {
    return System.currentTimeMillis();
  }
  
  public static BuilderExampleBuilder builder() {
    return new BuilderExampleBuilder();
  }
  
  public static class BuilderExampleBuilder {
    private long created;
    private boolean created$set;
    private String name;
    private int age;
    private java.util.ArrayList<String> occupations;
    
    BuilderExampleBuilder() {
    }
    
    public BuilderExampleBuilder created(long created) {
      this.created = created;
      this.created$set = true;
      return this;
    }
    
    public BuilderExampleBuilder name(String name) {
      this.name = name;
      return this;
    }
    
    public BuilderExampleBuilder age(int age) {
      this.age = age;
      return this;
    }
    
    public BuilderExampleBuilder occupation(String occupation) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }
      
      this.occupations.add(occupation);
      return this;
    }
    
    public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
      if (this.occupations == null) {
        this.occupations = new java.util.ArrayList<String>();
      }

      this.occupations.addAll(occupations);
      return this;
    }
    
    public BuilderExampleBuilder clearOccupations() {
      if (this.occupations != null) {
        this.occupations.clear();
      }
      
      return this;
    }

    public BuilderExample build() {
      // complicated switch statement to produce a compact properly sized immutable set omitted.
      Set<String> occupations = ...;
      return new BuilderExample(created$set ? created : BuilderExample.$default$created(), name, age, occupations);
    }
    
    @java.lang.Override
    public String toString() {
      return "BuilderExample.BuilderExampleBuilder(created = " + this.created + ", name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
    }
  }
}

Quelle: https://projectlombok.org/features/Builder

veysiertekin
quelle
lombok ist die Codegenerierung mit zusätzlichen Schritten. Aus diesem Grund verwirrt eine Reihe von statischen Analysewerkzeugen, da der endgültige Code, den sie lesen, nicht wie Java aussieht.
ThisCompSciGuy
2

Meiner Meinung nach ist der einfachste Weg, die innere statische Klasse anstelle einer separaten zu erhalten ,:

  1. Wenn Sie noch keinen Konstruktor haben, generieren Sie einen mithilfe des Dialogfelds Konstruktor generieren
  2. Verwenden Sie dann Konstruktor durch Builder ersetzen
  3. Verschieben Sie die neu erstellte Klasse mithilfe des Verschiebungs-Refactorings (Hotkey: F6) zurück zur ursprünglichen Klasse.
Gaket
quelle
1
Dieses Plugin scheint genau dies in nur einem Schritt zu erreichen: plugins.jetbrains.com/plugin/7354-innerbuilder
jivimberg
0

Als Ergänzung zur Antwort von @ CrazyCoder halte ich es für sehr nützlich zu wissen, dass sich oben rechts im Dialogfeld " Konstruktor durch Builder ersetzen " eine Konfigurationsschaltfläche befindet, mit der Sie das Präfix der Setter umbenennen können.

tibtof
quelle
0

Wenn Sie "zu viele Parameter" durch ein Builder-Muster ersetzen möchten, führen Sie "Parameterobjekt extrahieren" aus und konvertieren Sie dann den Konstruktor in den Builder, der in den anderen Antworten hier erwähnt wird.

Rogerdpack
quelle
0

Generieren Sie den Konstruktor der Klasse, indem Sie mit der rechten Maustaste auf Klasse Generieren> Konstruktor klicken. Drücken Sie dann unter Windows Strg + Umschalt + A, um die Aktion zu suchen, und geben Sie "Builder" ein. Wählen Sie "Konstruktor durch Builder ersetzen".

Vijay Nandwana
quelle
0

Ich habe festgestellt, dass der Effective Inner Builder die beste Option ist, da er Nullprüfungen hinzufügt und nur mit den definierten Elementen beginnen kann.

ThisCompSciGuy
quelle