Lenji Consumer i Supplier

3 min

U poslednje vreme često koristim nešto što nazivam lazy konfigurisanje. Da predstavim problem sledećim kodom:

public class Starter {
	private Foo foo;
	private Bar bar;

	public void start() {
		// ...
		foo = new Foo();
		// ...
		bar = new Bar(foo);
		// ...
	}
}

U pitanju je kompozija par komponenti i njihovo kreiranje.

Problem nastaje kada želimo da konfigurišemo instancu Foo. Dakle, negde nakon kreiranja instance Foo i njenog korišćenja u Bar treba je dohvatiti i dati korisniku da je konfiguriše. Očigledno, instancu ne možemo da dohvatimo pre poziva start(), tako da dodavanje gettera getFoo() nema smisla. Neka tradicionalna rešenja bi uključivala, na pr., nasleđivanje Starter klase i uvođenja apstraktne metode za konfiguraciju; no ne smatram da je to dobar razlog zašto bi klasu trebalo naslediti.

Consumer

Java8 dolazi sa Consumer funkcionalnom klasom, koja je sjajna za odgođena delovanja. Rešenje gornjeg problema se svodi da korisnik ne dohvata instancu foo, već da dostavlja kod (kao lambdu) koji će se baviti instancom, ali tek kada za to bude došlo vreme! Gornji kod sada postaje:

public void start(Consumer<Foo> fooConsumer) {
	// ...
	foo = new Foo();
	fooConsumer.accept(foo);
	// ...
}

Idemo dalje: ovde mi se ne dopada to što start() prima argument; kako se povećava broj komponenti rastao bi i broj argumenata, a svakako bi bilo ružno za korišćenje. Umesto toga možemo da koristimo builder obrazac i napravimo fluent interfejs:

public Starter withFoo(Consumer<Foo> fooConsumer) {
	fooConsumers.add(fooConsumer);
	return this;
}

Ne samo što smo uklonili argument(e) iz start() metode, već je omogućeno da korisnik dostavi više takvih blokova koda čuvajući te consumere u listi. Korišćenje:

starter
	.withFoo(foo -> foo.feature1(true))
  	.withFoo(foo -> foo.feature2(false))
  	.start();

Naravno, moglo je sve odraditi i bez lista i samo sa jednom pozivom withFoo(). Međutim, ovako dozvoljavamo da se 1) konfiguracija definiše na različitim mestima; i 2) da se može izdeliti na sitnije, logički bliske, korake.

Supplier

Još jedna stvar ovde se može “ulenjiti”. Bar prima instancu Foo. Šta ako se desi da se foo promeni nakon što ga je Bar preuzeo? Drugim rečima, moramo da vodimo računa da nakon što se kreira instanca Bar ne smemo da menjamo foo.

U pomoć dolazi druga Java8 klasa: Supplier. Menjamo konstruktor za Bar:

public Bar(Supplier<Foo> fooSupplier) {
	this.fooSupplier = fooSupplier;
}

U konstruktoru ništa drugo i ne smemo da radimo što se tiče foo, inače ovo gubi smisao. Sada se Bar kreira ovako:

bar = new Bar(() -> foo);

Šta god da se sada desi sa foo i nakon što smo kreirali Bar neće uticati na izvršavanje.

Još jedan primer za Consumer

Čest slučaj je registracija nekih klasa koje se kasnije instanciraju po potrebi:

registerModule(MyModule.class);
registerModule(ThatModule.class);

Ponovo je potrebno konfigurisati instancu nekog modula, ali tek kada se on kreira, što može biti bilo kada u toku rada programa. I ovaj slučaj je zgodan za Consumer, koji se može dodati kao opcioni argument metode:

registerModule(MyModule.class, module -> {
	module.setColor(123);
	module.setSize(456);
})
registerModule(ThatModule.class);

Sve što treba je da čuvamo i Consumer pored klase na koju se odnosi, te i da ga pozovemo jednom kada kreiramo instancu klase.

Što je lepo biti lenj 🙂

🧧
Nisam definisan svojim stavovima. Stavove usvajamo, menjamo, nadograđujemo, ali oni ne čine nas same. Manje je važno da li se slažemo, koliko da se razumemo.