A builderről előbb beszéltem, és láttuk, hogy így lehet használni:
Icecream icecream=new IcecreamBuilder()
.addScoop("csoki")
.addSauce("csokiöntet")
.addSauce("málnaöntet")
.build();
Egyelőre még ez tök furán néz ki, de kezdük kis lépésekben! Szóval a builder egy osztály, igaz? Hozz létre egy új osztályt:
public class IcecreamBuilder {
}
Aztán, van neki egy egy csomó metódusa, amivel "felveszi a rendelést":
.addScoop("csoki")
.addScoop("málna")
.addScoop("citrom")
.setWaffel("édes tölcsér")
.addTopping("tejszínhab")
.addSauce("csokiöntet")
Kezdjük tehát!
public class IcecreamBuilder {
// lehetnek default-ok, azaz jó kezdőértékek
protected String waffel="sima tölcsér";
public void setWaffel(String waffel) {
// csak eltároljuk - "felvesszük a rendelést"
this.waffel=waffel;
}
}
Oké, de a fagyik... abból többet is lehet választani! Akkor mi kell ide? Egy konténer - pont úgy, ahogy a kocsma példában gyűjöttük, hogy ki mit ivott!
public class IcecreamBuilder {
protected String waffel="sima tölcsér";
protected List<String> scoops=new ArrayList<>();
public void setWaffel(String waffel) {
this.waffel=waffel;
}
public void addScoop(String scoop) {
// adjuk hozzá a gombóc listához a választott gombócot
scoops.add(scoop);
}
}
A feltétek és az öntetek ugyanez a logika. Végül pedig - végül pedig tényleg össze kell építeni azt a fagyit! Hogyan? Hát az Icecream konstruktorával! Amibe nem 100 paramétert, hanem egyetlen paramétert, magát a buildert adjuk meg!
public class IcecreamBuilder {
protected String waffel="sima tölcsér";
protected List<String> scoops=new ArrayList<>();
public void setWaffel(String waffel) {
this.waffel=waffel;
}
public void addScoop(String scoop) {
// adjuk hozzá a gombóc listához a választott gombócot
scoops.add(scoop);
}
public Icecream build() {
return new Icecream(this);
}
}
public class Icecream {
public Icecream(IcecreamBuilder builder) {
// na most a builderben összerakott fagyirendelés
// minden részlete itt van a builderben,
// tehát ebben a konstruktorban tökéletesen felépíthetük
// a kívánt fagyit
}
}
Elég macerás, hogy most akkor a builder miatt mindenhez két osztályt kell csinálni. Tudod mit? Nem kell! Alapvetően nem jó ötlet egy file-ba több osztályt pakolni, de a builder pattern kivétel. Itt az a szokás, hogy a fagyi és a fagyisnéni egy file-ban van - abból az okból kifolyólag, hogy innentől kizárólag a fagyisnéninek szabad fagyit csinálnia. Elvégre is, valóságban sem szoktunk a fagyispult másik oldalára osonni, és kiszolgálni magunkat, vagy Te már csináltál ilyet? :)
Tehát:
public class Icecream {
private Icecream(IcecreamBuilder builder) {
// na most a builderben összerakott fagyirendelés
// minden részlete itt van a builderben,
// tehát ebben a konstruktorban tökéletesen felépíthetük
// a kívánt fagyit, de most csak kiírok pár dolgot
System.out.println("Tölcsér: "+builder.waffel);
System.out.println("Ennyi gombóc: "+builder.scoops.size());
}
public static class Builder {
protected String waffel="sima tölcsér";
protected List<String> scoops=new ArrayList<>();
public void setWaffel(String waffel) {
this.waffel=waffel;
}
public void addScoop(String scoop) {
// adjuk hozzá a gombóc listához a választott gombócot
scoops.add(scoop);
}
public Icecream build() {
return new Icecream(this);
}
}
}
No most, akkor hogyan lesz Icecream?
// csinálunk egy példányt a builderből - a "rendelésünk"
Icecream.Builder builder=new Icecream.Builder();
// elmondjuk, mit kérünk
builder.addScoop("csoki");
builder.addScoop("narancs");
// végül megkérjük a buildert, hogy építsen ennek megfelelő fagyit
Icecream icecream=builder.build();
Hmm. Működni működik, de nem mondhatnám, hogy kézre áll...
De mi az, hogy fluent? A fluent azt jelenti, hogy "folyékony", mármint nem úgy, mint a megolvadt fagyi, hanem inkább úgy, ahogy az ember folyékonyan beszél egy másik emberhez.
Nem-fluent:
Géza, nyisd ki az ajtót!
Géza, kapcsold fel a villanyt!
Géza, locsold meg a virágot!
Géza, kapcsold le a villanyt!
Géza, csukd be az ajtót!
Fluent:
Géza,
-nyisd ki az ajtót,
-kapcsold fel a villanyt,
-locsold meg a virágot
-kapcsold le a villanyt,
-csukd be az ajtót.
Hm, kezded érezni a különbséget, ugye?
Nem-fluent - ez tényleg nem más, mint a builder függvényeinek hívogatása:
IcecreamBuilder builder=new IcecreamBuilder();
builder.addScoop("csoki");
builder.addSauce("csokiöntet");
builder.addSauce("málnaöntet");
Icecream icecream=builder.build();
Fluent - na ez jobban néz ki, mert nem kell ismételni, hogy Géza, Géza Géza, izé builder, builder, builder:
Icecream icecream=new IcecreamBuilder()
.addScoop("csoki")
.addSauce("csokiöntet")
.addSauce("málnaöntet")
.build();
No tehát, ha valahol egymás után egy objektum metódusait hívogatjuk, és ekkor újra meg újra le kell írni a példány nevét (Géza, Géza, Géza) az nem fluent.
Ha pedig ezt sikerül megspórolni, akkor az fluent.
Ó, ez lenyűgözően egyszerű! A setXXX-nek nem voidnak kell lennie, hanem vissza kell adnia a példányt saját magát (azaz a this-t).
Tehát, ahelyett, hogy void lenne a sok setXXX meg addXXX, adjanak vissza egy ... buildert, mégpedig saját magát:
public static class Builder {
protected String waffel="sima tölcsér";
protected List<String> scoops=new ArrayList<>();
public Builder setWaffel(String waffel) {
this.waffel=waffel;
return this; // add vissza magad
}
public Builder addScoop(String scoop) {
// adjuk hozzá a gombóc listához a választott gombócot
scoops.add(scoop);
return this; // add vissza magad
}
public Icecream build() {
return new Icecream(this);
}
}
Tehát, akkor most az összes setXXX meg addXXX - mivel függvény - ezért visszaad valamit, mégpedig egy Buildert, tehát a visszaadott értéknek van setXXX meg addXXX metódusa, amik visszaadnak egy Buildert, tehát lehet őket folyton-folyvást folytatni.
// csinálunk egy példányt a builderből - a "rendelésünk"
Icecream.Builder builder=new Icecream.Builder();
builder.addScoop("csoki"); //ennek az eredménye a builder maga
builder.addScoop("narancs"); //ennek az eredménye a builder maga
// végül megkérjük a buildert, hogy építsen ennek megfelelő fagyit
Icecream icecream=builder.build();
// mivel az addScoop visszaadja a buildert, írhatjuk így is:
Icecream.Builder builder=new Icecream.Builder().addScoop("csoki");
// végül megkérjük a buildert, hogy építsen ennek megfelelő fagyit
Icecream icecream=builder.build();
Az addXXX és setXXX sorokat érdemes új sorba írni, hogy ne legyenek nagyon hosszú sorok:
// mondtam már, hogy minden visszaadja a buildert?
Icecream.Builder builder=new Icecream.Builder()
.addScoop("csoki")
.addScoop("narancs")
.setWaffel("édestölcsér");
// végül megkérjük a buildert, hogy építsen ennek megfelelő fagyit
Icecream icecream=builder.build();
Ha minden visszaadja a builder-t, akkor a build metódust is lazán meg lehet hívni egyből! Azonnban, a build metódus már az igazi fagyit adja vissza:
// MINDEN visszaadja a buildert, a végén a build pedig az új fagyit.
Icecream icecream=new Icecream.Builder()
.addScoop("csoki")
.addScoop("narancs")
.setWaffel("édestölcsér")
.build();
Tulajdonképpen a builder példányra sosincs szükségünk, nem is tettem egy változóba az értékét - hiszen nekünk a fagyi a lényeg! Tehát ez történik:
new Icecream.Builder() // létrejön egy új builder, a tölcsér típusa síma tölcsér
.addScoop("csoki") // betesszük a gombócok listájába hogy csoki, visszaadjuk a buildert
.addScoop("narancs") // betesszük a gombócok listájába hogy narancs, visszaadjuk a buildert
.setWaffel("édestölcsér") // beállítjuk a tölcsér típusát, visszaadjuk a buildert
.build(); // meghívjuk az Icecream konstruktorát, és visszaadjuk az új Icecream példányt
Hmm, nem rossz, ugye? A következő részben ezt megcsináljuk a Window osztályunkkal!