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