piątek, 4 stycznia 2013

Fluent builder do generowania fluent builderów cz.1

Na samym początku dla wszystkich, którzy przykładają duża wagę do definicji, nie będzie tutaj mowa o wzorcu builder w pełnej swojej krasie, a raczej o jego uproszczonej wersji, która jest jednak częściej spotykana.

Stosując TDD, prędzej czy później dojdziemy do sytuacji, w której potrzebujemy spreparować sobie jakiś obiekt (niech to będzie np. encja domenowa) do testów. Jeśli obiekt ma dobrą enkapsulację, to nie mamy bezpośredniego dostępu do wszystkich pól, co za tym idzie niektóre z nich zostaną poprawnie zainicjowane dopiero po wywołaniu szeregu metod domenowych. Przykładowo jeśli potrzebujemy encje Order, ale jako zrealizowane zamówienie (pole realized = true), tylko i wyłączenie na potrzebuje testów, to dla mnie stratą czasu jest wywoływanie wielu metod (które nie rzadko są dość skomplikowane) tylko po to żeby ta flaga miała odpowiednią wartość.

Drogą na skróty jest tutaj użycie buildera, który znajduje się wewnątrz danej klasy, dzięki temu nie musimy sztucznie dodawać metod "set" do obiektu. Tworzenie takich builderów jest dość nudnym zajęciem dlatego proponuje generator builderów, który zrobi to za nas.

Dla klasy:
public class Sample implements Serializable {

 private static final long serialVersionUID = 2631948252607310591L;
 private final String SOME_CONSTANCE = "SOME_CONSTANCE";

 private int id;
 private String name;
 private Map<String, List<Integer>> map;
}
Wykorzystując generator, który domyślnie drukuje do konsoli:
SimpleBuilderGenerator.forClass(Sample.class)
    .withMethodPrefix("with")
    .printBuilder();
Po wklejeniu tego co zostało wygenrowane do głownej klasy otrzymujemy:
public class Sample implements Serializable {

 private static final long serialVersionUID = 2631948252607310591L;
 private final String SOME_CONSTANCE = "SOME_CONSTANCE";

 private int id;
 private String name;
 private Map<String, List<Integer>> map;
 
 /** 
  * Fluent builder for Sample
  * @formatter:off
  */
 public static Builder builder() {
  return new Builder();
 }

 public static class Builder {

  private final Sample sample = new Sample();

  public Builder withId(int id) { sample.id = id; return this; }
  public Builder withName(String name) { sample.name = name; return this; }
  public Builder withMap(Map<String, List<Integer>> map) { sample.map = map; return this; }

  public Sample build() { return sample;}
 }
 /** @formatter:on */
}
I samo wykorzystanie:
Sample sample = Sample.builder().withName("name").build();
Wszystko fajnie pięknie, ale taki builder ma jedną zasadniczą wadę - jego kod znajduje się w klasie produkcyjnej. Wymaga to od programisty przestrzegania zasady, żeby nie używać tego mechanizmu w kodzie produkcyjnym.

Właściwie cały ten post to preludium do właściwej treści, czyli tworzenie builderów, które są poza klasami produkcyjnymi i nie wymagają setterów, ale o tym w części 2.

Kod źródłowy generatora można znaleźć tutaj.

Brak komentarzy:

Publikowanie komentarza