Strona główna > Java > Mała, ciekawa i inspirująca biblioteka do budowania Builderów

Mała, ciekawa i inspirująca biblioteka do budowania Builderów

Dziś wpadłem na informacje o bibliotece make-it-easy, ten link pokazuje do czego to służy. Streszczając jest to coś co ma uprościć pisane kodu pełniącego role podobną jak to jest w przypadku wzorca builder. No i tyle :).
Urzekła mnie w tym prostota wyrażenia tego co chcemy budować oraz opis jak to chcemy robić. Sam też niedawno próbowałem wdrożyć swój pomysł na to, ale utknąłem przez to że chciałem zrobić to zbyt prosto, zatrudniając przy tym zbyt ciężkie rozwiązania (refleksja i generowanie proxy).
Tak więc dzięki tej bibliotece można robić rzeczy które pokrótce opiszę. Na koniec dostarczę prostą implementacje, która używa trochę inne notacji.

Mając zdefiniowane przykładowe klasy, których budowanie chcemy uprościć:

public class Person {

	private String firstname;
	private String surname;
	
	private Set<Address> addresses;
	
	// getters, setters
}
...
public class Address {

	private String street;

	// getters, setters
}

Korzystając z biblioteki make-it-easy definiujemy opis tego jak chcemy to budować:

class PersonMaker {
	
	public static final Property<Person,String> firstname = Property.newProperty();
	public static final Property<Person,String> surname = Property.newProperty();
	public static final Property<Person,Set<Address>> address = Property.newProperty();
	
	public static final Instantiator<Person> Person = new Instantiator<Person>() {
	    public Person instantiate(PropertyLookup<Person> lookup) {
			Person person = new Person();
			person.setFirstname(lookup.valueOf(firstname, "Jan"));
			person.setSurname(lookup.valueOf(surname, "Kowlaski"));

			person.setAddresses(lookup.valueOf(address, new HashSet<Address>()));
			
	        return person;
	    }
	};
	
	public static final Property<Address,String> street = Property.newProperty();
	
	public static final Instantiator<Address> Address = new Instantiator<Address>() {
	    public Address instantiate(PropertyLookup<Address> lookup) {
			Address address = new Address();
			address.setStreet(lookup.valueOf(street, "ulica"));
	        return address;
	    }
	};

}

W jednej klasie został zawarty opis tworzenia obu klas. Co jest istotne to: elementy klasy Property mówiące jakie atrybuty możemy ustawiać, oraz Instantiator mówiące o tym jak końcowy obiekt ma być zbudowany (dostarcza także opis domyślnych wartości).

Użycie tego w następujący sposób.
I tak do prostego zbudowania domyślnego obiekty „Jan Kowalski” wystarczy tyle:

Person somebody = MakeItEasy.make(MakeItEasy.a(PersonMaker.Person));

Można ograniczyć trochę ilość kody delegując cześć do statycznych importów. Tak też jest następnym przykładzie, gdzie ustawiamy jedną wartość:

Person somebody = make(a(PersonMaker.Person,with(PersonMaker.firstname, "Tomasz")));

A jak już chce się wykorzystać wszytki zdefiniowane elementy to można tak:

Person somebody = = make(a(PersonMaker.Person,
				with(PersonMaker.surname, "Malinowski"),
				with(PersonMaker.firstname, "Tomasz"),
				with(PersonMaker.address,
						setOf(
						an(PersonMaker.Address, with(PersonMaker.street, "Biała")),
						an(PersonMaker.Address, with(PersonMaker.street, "Niebieska"))
			        ))));

Biblioteka dostarcza jeszcze parę sztuczek, ale to z grubsza tyle. W założeniu twórcy ma ułatwiać tworzenie obiektów na potrzeby testów. I chyba tam dobrze pasuję. To co mi osobiście przeszkadza to potrzeba korzystania z statycznych metod, czy importów. Trzeba znać zewnętrzny zestaw metod, oraz strukturę zdefiniowanego przez nas „makera”. Przy dłuższej pracy z tym pewnie można się oswoić, ale pierwsze zetknięcie z kodem przez osobę nieznającej tego rozwiązania może trochę przestraszyć :), choć to może tylko moje czepianie.
To jednak co jest genialne w tym rozwiązaniu to: rozdzielenie definicji tego co można ustawiać oraz jak to będzie ustawiane w budowanym obiekcie.
Tego uproszczenia brakowało mi kiedy chciałem napisać coś podobnego. Pamiętam że uparcie trzymałem się tego aby zdefiniować tylko tą pierwszą cześć. Miała ona mówić co można ustawiać, a to jak to będzie robione załatwić refleksją i domyślnym ustawianiem wartości na podstawie nazwy. Byłoby to niby bardziej zwięzłe, ale przy rozwiązaniu z make-it-easy mamy większa swobodę w pracy z nietypowymi obiektami.
Tak więc użyłem tego rozdzielenia; i zaimplementowałem prostą wersje swojego pomysłu. Poniżej jest definicja mojego buildera (ograniczenie tylko do jednej klasy).

class PersonBuilder extends MyBuilder<Person> {

	public static PersonBuilder personBuilder() {
		return new PersonBuilder();
	}
	
	public final WithProperty<String, PersonBuilder> withFirstname = newWithProperty(this);
	public final WithProperty<String, PersonBuilder> withSurname = newWithProperty(this);
	
	protected Person getInstant() {
		Person person = new Person();
		person.setFirstname(valueOf(withFirstname, "Jan"));
		person.setSurname(valueOf(withSurname, "Kowlaski"));
		return person;
	}
	
}

A używać tego można tak:

Person somebody = PersonBuilder.personBuilder()
				.withSurname.set("Malinowski")
				.withFirstname.set("Tomasz")
				.build();

To co mi się podoba w tym podejściu to, że nie ma potrzeby brudzenia kodu technicznymi elementami, tak jak to jest w przykładzie z użyciem MakeItEasy. Oraz to, że składowe elementy są lepiej dostępne poprzez podpowiadanie kodu w IDE. No i że sam to napisałem ;).
Rozwiązanie nie ma tak szerokich możliwości jak biblioteka, którą się inspirowałem (brak kopiowania definicji builderów, odpowiednik but() w make-it-easy) , ale wielkość kodu, który to ożywia, jest mała. Na tym etapie powinno to załatwiać większość potrzeb związanych z pisaniem builderów; i stanowi to także dobrą bazę jeśli chce się czegoś więcej.

public abstract class MyBuilder<E> {
	
	protected abstract E getInstant();
	
	public E build() {
		return getInstant();
	}
	
	protected <V,M> V valueOf(WithProperty<V,M> withProperty, V defoult) {
		if (withProperty.value == null) {
			return defoult;
		} 
		return withProperty.value;
	}
	
    protected <V,M> WithProperty<V,M> newWithProperty(M maker) {
        return new WithProperty<V,M>(maker);
    }
    
	public class WithProperty<V,M> {
		
		private V value = null;
		private M myBuilder = null;
		
		private WithProperty(M maker) {
			this.myBuilder = maker;
		}

	    public M set(V value) {
	    	this.value = value;
	    	return this.myBuilder;
	    }
	}
	
}
Kategorie:Java Tagi: ,
  1. Brak komentarzy.
  1. No trackbacks yet.

Dodaj komentarz