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...
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.
OdpowiedzUsuń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.