Valahogyan csak bele kellene tenni a vezérlőket az ablakba is! Ez pont az a feladat, mint a fagyigombócok: a buildernek össze kell gyűjteni a vezérlőket, és amikor az ablak létrejön, akkor beletenni az ablakba őket.
Az ablakba a JFrame add(Component comp) metódusát használtuk. Tehát gyűjtsük egy Component-eket tartalmazó listába, hogy miket akarunk beletenni, majd a Window konstruktorában tegyük is bele a komponenseket az ablakba:
public class Window extends JFrame {
private static final long serialVersionUID = 1L;
private Window(Builder builder) {
setTitle(builder.text);
// dobáljuk bele az ablakba a vezérlőket
for (Component component:builder.components) {
add(component);
}
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static class Builder {
private String text;
private List<Component> components=new ArrayList<>();
public Builder text(String text) {
this.text=text;
return this;
}
// egy vezérlőt ide!
public Builder add(Component component) {
components.add(component);
return this;
}
public Window build() {
return new Window(this);
}
}
}
Mindezek után a Window builderének lesz egy add metódusa, ami elfogad egy Label-t, amit a Label builderével hozunk létre:
public class WindowTest {
public static void main(String[] args) {
new Window.Builder()
.text("Hello")
.add(new Label.Builder().text("Hello Geza!").build())
.build();
}
}
Khm. Logikus, de azért még elég sokat kell gépelni, nem?
Most tényleg, mégegy izé... minta? Mintamókus? Igen! Ezek a tervezési minták remek dolgok! Tudtad, hogy nem csak úgy valaki "felfedezte" őket? Hanem négy okos ember gyűjtögette ezeket össze, az alapján, hogy a nagyvilágban hogyan szoktak okos programozók megoldani tipikus feladatokat.
A négy hős név szerint Erich Gamma, Richard Helm, Ralph Johnson, és John Vlissides. Ők adtak nevet ezeknek a dolgoknak, és nekik köszönhetjük, hogy nem azt kell mondani, hogy "tudod olyan sok ponttal egymás után írjuk a metódusokat aztán meg meghívjuk a build-et", hanem elég annyit mondani, hogy "builder pattern" és a világon mindenki tudja, hogy miről beszélünk.
Ezekről a patternekről írtak egy könyvet, melynek címe: Design Patterns: Elements of Reusable Object-Oriented Software. Nos, mivel ez egy baromira hosszú név, ezért kellett valami emlékezetes név. A négy szerző neve is nagyon hosszú - ezért rájuk ragadt a "Gang of Four" azaz "négyek bandája" név, és ma az emberek a könyvet úgy emlegetik, hogy GoF book. Remélem sokat fogsz találkozni ezzel a kifejezéssel a következő években :)
A factory method azt jelenti, hogy csinálunk egy olyan metódust, ami létrehoz egy objektumot. Ilyen volt Feri, a pultos a kocsma példában, iszen Ital példányokat hozott létre.
Most itt tartunk a guis dologgal:
public class WindowTest {
public static void main(String[] args) {
new Window.Builder()
.text("Hello")
.add(new Label.Builder().text("Hello Geza!").build())
.build();
}
}
és szerintem két dolgot még meg lehetne spórolni:
A new megspórolása pont egy factory method! Szóval, ha csinálunk egy metódust, ami létre tud hozni például egy Label.Builder() példányt, akkor a new-t meg lehet spórolni az ablak kódjából.
Szóval a factory method:
public Label.Builder label() {
return new Label.Builder();
}
És akkor ezt használva a new Label.Builder() helyett elég annyit írni, hogy label():
public class WindowTest {
public static void main(String[] args) {
new Window.Builder()
.text("Hello")
// ez most sokkal rövidebb lett
.add(label().text("Hello Geza!").build())
.build();
}
}
Hm, vajon értelmes-e olyan label, aminek nincsen szövege? A label azt jelenti, hogy cimke, és izé... olyan cimke amin nincs szöveg az értelmetlen, nem? Mi lenne, ha eleve olyan factory methodot is csinálnánk, ami lehetővé teszi, hogy a label szövegét is beállítsuk?
Ez ugyanúgy létrehozza a buildert, de meghívja rajta a text metódust és beállítja a paraméterben kapott szöveget:
public Label.Builder label(String text) {
return new Label.Builder().text(text);
}
És akkor ezt használva a new Label.Builder().text("Hello Geza!") helyett elég annyit írni, hogy label("Hello Geza!"):
public class WindowTest {
public static void main(String[] args) {
new Window.Builder()
.text("Hello")
// ez most még sokkal rövidebb lett
.add(label("Hello Geza!").build())
.build();
}
}
Vajon akkor minek ez az egész vacakolás? Miért nem úgy kezdtük, hogy egyetlen label metódust csinálunk, ami rögtön legyártja a JLabelt, és készen vagyunk? Valami ilyesmit, mint amit ide írtam, és akkor builder sem kellene, meg semmi sem kellene:
public JLabel label(String text) label {
return new JLabel(text);
}
Hm, ez izé - ésszerűnek tűnne! De a sötét oldal simább, bizonyosan oka volt, hogy nem így csináljuk! Az oka pedig az, hogy ezzel a megoldással a telescoping constructor problémát megismételnénk egy telescoping paraméterlista módon. Szóval, a megoldás szép, amíg egyetlen tulajdonsága van a labelnek, de ha már két dolog van, pld. szöveg és ikon, akkor háromféle factory methodot kellene csinálni:
public JLabel label(String text) label {
...
}
public JLabel label(String text,Icon icon) label {
...
}
public JLabel label(Icon icon) label {
...
}
Három tulajdonsághoz már hétfélét, négyhez már 15 félét... jajj, ez tényleg olyan mint a telescoping constructor, igaz?
No de a mi factory methodjaink buildert adnak vissza! Tehát utána nyugodtan használhatjuk a szép, builderes dolgokat, és bármennyi dolgot be lehet állítani ugyanazzal az egyetlen builderrel. Tehát nincs annyiféle konstruktor, vagy factory method, amennyi féle módon lehet paramétereket beállítani. Csak egy builder és egy factory method van, mégis sok mindent be lehet állítani tetszőlegesen:
public class WindowTest {
public static void main(String[] args) {
new Window.Builder()
.text("Hello")
// ez még mindig builderes:
.add(label("Hello Geza!").icon("budapest.jpg").valami("hello").valamimas("ize").build())
.build();
}
}
A builder hívásait akár írhatjuk több sorba is, így olvashatóbb. Mivel ez még mindig builder, tetszőleges sorrendben hívhatjuk meg a builder metódusokat, mégis rögtön látjuk a kódon, hogy a "budapest.jpg" az ikont állítja:
public class WindowTest {
public static void main(String[] args) {
new Window.Builder()
.text("Hello")
// ez még mindig builderes:
.add(
label("Hello Geza!")
.valami("hello")
.icon("budapest.jpg")
.valamimas("ize")
.build()
)
.build();
}
}
Nos annak sok értelme nincs, hogy a programunkban egy metódussal odébb söpörjük a "szemetet", és ugyanott vannak a factory methodok, mint a tényleges ablakunk kódja.
Annak lenne értelme, ha ezek nem is látszanának! Tehát dobjuk be őket egy osztályba!
public class Form {
public Label.Builder label(String text) {
return new Label.Builder()
.text(text);
}
}
A WindowTest meg extends Form - szóval megörökli az összes metódust, így a label() metódust szabadon használhatjuk - de nincs útban!
public class WindowTest extends Form {
public static void main(String[] args) {
new Window.Builder()
.text("Hello")
.add(label("Hello Geza!").build())
.build();
}
}
Csak a rend kedvéért - a new Window.Builder() is eléggé rejtélyes, tutira nem akarjuk ezt fejben tartani! Ennek is csináljuk egy factory methodot:
public class Form {
public Label.Builder label(String text) {
return new Label.Builder()
.text(text);
}
public Window.Builder window(String text) {
return new Window.Builder()
.text(text);
}
}
No ugye, alakul ez, egyre több szemetet söpörtünk be a szőnyeg alá! Már tényleg szinte csak a lényeg látszik:
public class WindowTest extends Form {
public static void main(String[] args) {
window("Hello")
.add(label("Hello Geza!").build())
.build();
}
}
layoutok (valami default flowlayout BR supporttal)
inset a windowra
data binding
amit eltitkoltunk: