czwartek, 26 września 2013

Fluent builder do generowania fluent builderów cz. 3

Opisany w części 2 generator buidlerów został gruntownie zrefaktoryzowany i ewoluował o poziom wyżej, jego użycie staje się jeszcze łatwiejsze. Korzystając z Annotation Processing Tool, mechanizm generowania klas został przeniesiony do procesora adnotacji, którego możemy na różne sposoby podłączyć do projektu.

Nie nudząc technicznymi szczegółami przejdźmy do przykładu. Klasę np. Order oznaczamy adnotacją @GenerateBuilder, która informuje procesor, że dla niej ma zostać wygenerowany builder:
@GenerateBuilder
public class Order {
 private List<OrderItem> items;
 private Date createDate;
 private boolean realized;

 public boolean isRealized() {
  return realized;
 }
 public void orderRealized() {
  // very complex implementation
  realized = true;
 }
}
Żeby w jakiś sposób automatycznie aktualizować istniejące buildery o nowe pola, które doszły w klasie Order (lub odpowiednio usuwać metody inicjujące nieistniejące pola), builder został rozbity na 2 klasy. Jedna z nich jest aktualizowana zawsze przy uruchomieniu procesora, druga generowana tylko raz, dzięki czemu możemy do niej dodawać własne metody budujące. Dla klasy Order powstaną następujące klasy:
public abstract class AbstractOrderBuilder<B> extends AbstractBuilder<Order, B> {
 public abstract B withItems(List<OrderItem> items);
 public abstract B withCreateDate(Date createDate);
 public abstract B withRealized(boolean realized);
 public B withItems(OrderItem... items){
  return withItems(new ArrayList<OrderItem>(Arrays.asList(items)));
 }
}
public abstract class OrderBuilder extends AbstractOrderBuilder<OrderBuilder> {
 public static OrderBuilder anOrder(){
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
}
Samo użycie może wyglądać następująco:
@Test
public void shouldCreateRealizedOrder() {
 // when
 Order order = anOrder().withRealized(true).build();
 // then
 assertTrue(order.isRealized());
}
Klasę OrderBuilder można rozszerzać, wywołując rzeczywiste metody domenowe, np:
public abstract class OrderBuilder extends AbstractOrderBuilder<OrderBuilder> {
 public static OrderBuilder create() {
  return AbstractBuilderFactory.createImplementation(OrderBuilder.class);
 }
 public OrderBuilder realized() {
  Order order = targetObject();
  // invoking real domain method
  order.orderRealized();
  // other methods
  return builder();
 }
}
@Test
public void shouldCreateRealizedOrder() {
 // when
 Order order = anOrder().realized().build();
 // then
 assertTrue(order.isRealized());
}
Do podłączenia procesora do projektu może zostać wykorzystany ant, maven, eclipse. Jest również możliwość użycia generatora w starym stylu, czyli wygenerowania ciała klasy buildera do konsoli.

Procesor może również poszukiwać klas z adnotacjami JPA (@Entity, @Ebeddable, @MappedSuperclass).

Do poznania szczegółów odsyłam do wiki projektu. Zachęcam do forkowania i dzielenia się uwagami.