sobota, 5 stycznia 2013

Fluent builder do generowania fluent builderów cz.2

Tak jak wspominałem w części 1. postaram się przedstawić, jak można tworzyć buildery do klas, które nie korzystają z setterów i nie są w ciele klasy.

Może od razu na konkretnym przykładnie dla klasy:
public class Order {

 private List<OrderItem> items;
 private Date createDate;
 private boolean realized;
 
 public void orderRealized(){
  //very complex implementation
  realized = true;
 }
}
Wykorzystując trochę przerobiony mechanizm generowanie builderów:
AbstractBuilderGenerator.forClass(Order.class)
    .withStaticCreate(true)
    .printBuilder();
Wynikiem jest interfejs dla naszego przyszłego buildera, w postaci abstrakcyjnej klasy i abstrakcyjnych metod:
public abstract class OrderBuilder extends AbstractBuilder<Order, OrderBuilder>{

 public abstract OrderBuilder withItems(List<OrderItem> items);
 public abstract OrderBuilder withCreateDate(Date createDate);
 public abstract OrderBuilder withRealized(boolean realized);
 public static OrderBuilder create(){
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
}
Kod z sysout'a należy skopiować do pliku i mamy klasę buildera, którą możemy umieścić sobie w dowolnym miejscu, najlepiej gdzieś w folderze od testów.
OrderBuilder dodatkowo został wygenerowany z metodą create, która wywołuje fabrykę tworzącą instancję buildera na podstawie jego abstrakcyjnego interfejsu. To jest miejsce gdzie się dzieje trochę czarnej magii, tj. refleksje, dynamiczne proxy, generics. Z drugiej strony, który framework na tym w dzisiejszych czasach nie bazuje? Fabryka wykorzystuje domyślny konstruktor do utworzenia obiektu docelowego (może być prywatny), ale jeśli klasa nie ma takiego konstruktora, to można użyć przeładowanej metody createImplementation i przekazać obiekt docelowy utworzony przez jakikolwiek konstruktor.

Samo wykorzystanie:
Order realizedOrder = OrderBuilder.create().withRealized(true).build();
Dzięki temu, że interfejs buildera nie jest tak de facto nie interfejsem a klasa abstrakcyjną mamy pełną swobodę w tworzeniu dodatkowych metod buildera, które mogą korzystać z metod domenowych obiektu.

Zmodyfikowany interfejs:
public abstract class OrderBuilder extends AbstractBuilder<Order, OrderBuilder> {

 public abstract OrderBuilder withItems(List<OrderItem> items);
 public abstract OrderBuilder withCreateDate(Date createDate);
 public abstract OrderBuilder withRealized(boolean realized);

 public OrderBuilder realized() {
  Order order = targetObject();
  order.orderRealized();
  // other methods
  return builder();
 }

 public static OrderBuilder create() {
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
}
Korzystając z metod targetObject i builder (zwracające odpowiednio referencje na obiekt docelowy i samego buildera), możemy konstruować dodatkowe metody tak, aby nie przerywały mechanizmu fluent. Wykorzystanie:
Order realizedOrder = OrderBuilder.create()
    .realized()
    .withCreateDate(new Date())
    .build();

Zapraszam do forkowania i komentowania co jeszcze można by tu było usprawnić, biblioteczka jest właściwie w powijakach. Najwygodniej byłoby gdyby generowanie nie odbywało się do sysouta, tylko od razu do pliku. Niestety nie mam żadnego doświadczenia w tworzeniu pluginów eclipsowych...

1 komentarz:

  1. No właśnie sam ostatnio szukałem jakiegoś plugina do Eclipsa do generowania Builderów (bo jestem leniwy i nie chce mi się ich palca klepać) i jedyne sensowne co znalazłem to plugin pisany przez chłopaków z Sabre: http://code.google.com/p/fluent-builders-generator-eclipse-plugin/ Generuje on jednak kod, który mi się średnio podoba.
    W moim aktualnym projekcie używamy Builderów tylko do testów i jej najprostrzej implementacji.
    Jak dla mnie +1 dla plugina do Eclipse / Idea.

    OdpowiedzUsuń