2. lépés - fixáljuk a telescoping constructor problémát

Hogy micsodát, kérem szépen? Na mindjárt! Először is, jó lenne egy konstruktor paraméter, ahol az ablak fejlécének a szövegét lehet átadni! Egy String!

public class Window extends JFrame {
    // default serial id baromsag
    private static final long serialVersionUID = 1L; 

    public Window(String text) {
        setTitle(text);
        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
    }
}

Ok, akkor setTitle(text), mezei paraméter, minden okés.

A konzisztencia

Azaz következetesség... az sajnos a Swing alkotóinak nem volt az erőssége. Az ablak fejécének a szövege setTitle, egy label szövege setText, és tutira van valami, ahol setValue-t használnak. Ha már itt vagyunk, legyünk logikusak: és minden olyan paramétert ami valaminek a szövege, nevezzünk text-nek.

Aztán az is jó lenne ám, ha néha még ikont is lehetne beállítani az ablaknak. Meg persze az ablak tartalmát. Meg ezt. Meg azt. Meg amazt is - így könnyen eljutunk 20-30 konstuktor paraméterig.

Persze ez nem kényelmes, mert legtöbbször csak az ablak szövegét akarjunk beállítani. De másszor meg mást is, így egyszerűen kedveskedünk magunknak annyival, hogy többféle konstruktort csinálunk - más-más paraméterlistával.

A Swing készítői is így tettek, így húhú, van pár variáció:

A JFrame konstruktorai

A JLabel esetén egészen kreatívok voltak:

A JLabel konstruktorai

A konzisztencia hiánya

Igazán szemfüles olvasóink észreveszik, hogy a JLabel egyik konstruktorában az ikon paraméter neve image, míg az utolsó konstruktorban icon. Hááát kocsmában csinálták ezt???

Szóval van sok konstruktorunk. Ami jó ötletnek tűnt, csak az a baj, hogy hamar a visszájára fordul. Ezt a dolgot, amikor sok-sok konstruktorunk van, egyre több plusz paraméterrel, na ezt nevezik úgy, hogy telescoping constructors (azaz "teleszkópos" konstruktorok). A szoftvertechnológia jelenlegi tudása szerint ez tervezési hibának számít, mert az alábbi két problémát hordozza magával.

A paramétereknek nincs kontextusa

Te kitalálod, hogy mit csinál az alábbi kód?

JLabel helloLabel=new JLabel(valami,akarmi);

Bevallom, nekem fogalmam sincs. Először is, meg kellene nézni a valami és az akarmi változó típusát, utána kitalálni, hogy vajon melyik konstruktor paramétereihez stimmelnek, és máris tudjuk, hogy ez, izé... vagy a 4. vagy az 5. konstruktor, szóval vagy ikont adunk meg valami vízszintes igazítással, vagy szöveget vízszintes igazítással... argh!

Nincs elég paraméter

Igaz, hogy van vagy 4-5 konstruktor, de a konstruktorban még mindig nincsen meg egy csomó egyéb paraméter, méret, szín, betűtípus, ezerféle dolog! Ezért ezeknek settere van, ahogy azt a 07-es részben láttuk.

Akkor most vajon a setText-el, vagy a megfelelő konstruktor hívásával kell beállítani a szöveget? Tulajdonképpen mindkettő működik... de ez pontosan kétszer több lehetőség és bonyolítás, ugye? Ráadásul most fejben fogjuk tartani, hogy éppen mit lehet konstruktorral, és mit lehet setterrel állítgatni? Jajj!

Az 1990-es évek megoldása

Igazából úgy gondolták megoldani ezeket a problémákat, hogy minden dologra van setter, ugyanakkor a leggyakrabban használt dolgokra van konstruktor paraméter is, így a setter hívását meg lehet spórolni, így:

JLabel helloLabel=new JLabel();
helloLabel.setText("Hello!");

és megspórolva a setText-et a konstruktor paraméterrel:

JLabel helloLabel=new JLabel("Hello!");

Nos, szakértő marketingesek erre azt mondanák, hogy 50% kód-megtakarítás! Pont feleakkora a program! Tehát ügyesek voltunk! Bár szerintem Dulifulinak biztos más lenne erről a véleménye...

A mai évek megoldása

A mai évek megoldása a builder pattern. A pattern azt jelenti, hogy "minta", és jelen esetben nem azt jelenti, hogy csíkos, avagy pöttyös lesz-e az osztályunk. A szó a design-patternre (tervezési mintára) utal, ami pedig olyan jól bevált módszerek gyűjteménye, amire manapság a modern szoftverek épülnek.

A design pattern

A gyakorlatban jól bevált 10-20 féle design pattern közül szép lassan megismerkedünk párral. Csak amire szükségünk lesz - hisz csak annak van értelme, amit használunk! Érdemes kiguglizni a design patterns for java kifejezést, ha érdekelnek a részletek.

Mint ahogy az OOP a való élet objektumait modellezik, a legtöbb design pattern a való élet megoldásait modellezi. A builder azt jelenti, hogy "építő".

Te már voltál fagyizóban, igaz? No, de tudsz fagyit készíteni? Én nem tudok fagyit készíteni, mégis, ha fagyit szeretnék, tudom, hogy mit kell csinálni: egy fagyizóba kell menni, és a fagyis néniek elmondani, hogy:

és ez után lesz egy fagyim!

Hogy ennek mi a köze a konstruktorainkhoz? Elég sok! Először is, nagyon nehéz lenne olyan konstruktort csinálni, ami fagyit készít, hiszen előre nem tudjuk, hogy mennyi gombócot kell legyártani. Akkor csináljunk 1, 2, 3, 4 gombócos konstruktort? Meg 1 gombóc + feltét, meg 1 gombóc + feltét 1 + feltét 2 konstruktort is, hiszen van, aki tejszínhabot kér, van aki öntetet... brrr....

Elég sok gond van itt tehát:

Na ezt paraméterezgesse valaki! Sokkal célszerűbb megoldás az, ha létrehozunk egy builder osztályt - az IcecreamBuilder-t, aminek van egy csomó metódusa, amivel mindent be lehet állítani, majd a végén a build metódusát meghívva létrejön a megfelelő fagyi - egy Icecream példány.

Kódban ez így nézne ki:

Icecream icecream=new IcecreamBuilder()
.addScoop("csoki")
.addScoop("málna")
.addScoop("citrom")
.setWaffel("édes tölcsér")
.addTopping("tejszínhab")
.addSauce("csokiöntet")
.build();

Umm, ez erősen hasonlít arra, amit a fagyisnéninek mondunk, igaz? :) Na ez a builder pattern - azaz mezei Java osztályok és mezei Java metódusok okos felhasználása abból a célból, hogy valamit sokkal kifejezőbben tudjunk leprogramozni.

Ó, és még valami: ezek az addIzék meg minden más is metódus hívás. Ez két okból jó:

Na akkor, hogy nézne ki egy mezei 1 gombóc csoki, az alap tölcsérben?

Icecream icecream=new IcecreamBuilder()
.addScoop("csoki")
.build();

Csoki, dupla öntettel

Icecream icecream=new IcecreamBuilder()
.addScoop("csoki")
.addSauce("csokiöntet")
.addSauce("málnaöntet")
.build();

Csak egy kis tejszínhab:

Icecream icecream=new IcecreamBuilder()
.addTopping("tejszínhab")
.build();

Akkor sincs baj, ha össze-vissza mondjuk:

Icecream icecream=new IcecreamBuilder()
.addTopping("tejszínhab")
.addSauce("málnaöntet")
.addScoop("csoki")
.build();

Lehet össze-vissza - hiszen a fagyit még nem kezdtük el összerakni, csak majd a build híváskor, addig csak a rendelést vettük fel.

Hm, ez egész jó!

Tehát akkor az Icecream (a fagyi) amit felépítünk, és az IcecreamBuilder (a fagyisnéni) aki "összeépíti" a kívánságainknak megfelelő fagyit.

No de hogyan tudunk ilyen buildert csinálni?

A builder pattern

A builder pattern lehetővé teszi, hogy egy sok-sok tulajdonsággal rendelkező objektumot ne a konstruktorával (és ezer paraméter kusza átadásával) hozzunk létre, hanem egy külön objektum-felépítő osztállyal.