Dobré praktiky vytváření objektů
-
Zvažte statickou tovární metodu místo konstruktorů.
-
Uvažujte o "budovateli" (builder) namísto konstruktoru s mnoha parametry.
-
Vynuťte singleton pomocí soukromých konstruktorů (nebo
enum
). -
Zabraňte přímé instanciaci pomocí soukromého konstruktoru.
-
Upřednostněte vložení závislostí (dependency injection) před pevným propojením.
-
Vyhněte se zbytečným objektům.
-
Eliminujte zastaralé odkazy na objekty.
-
Vyhněte se finalizátorům (metodám
finalize()
) a čističům.
Tovární metoda namísto konstruktoru
-
Consider static factory method instead of constructors
-
Josh Bloch: "Java efektivně, Tip 1: Používejme statickou tovární ("výrobní") metodu namísto přímého volání konstruktoru (evt. s parametry)"
-
Takových metod může být více a mohou se jmenovat různě (výstižněji) - viz
Person createMale()
-
Nemusí vrátit objekt za všech okolností, při všech možných voláních s různými parametry - někdy mohou na rozdíl od konstruktoru vrátit
null
-
Metoda může vrátit již existující instanci - to je klíčová výhoda, např. u singletonu nebo u skladiště (poolu) objektů - skladiště objektů šetří čas a výkon, singleton je bezpečný
Tovární metoda: variabilita vracených typů
-
Metoda nemusí vrátit objekt přesně stejného typu, jako je deklarován, může vrátit i objekt podtřídy, potomka - např. metoda
static Person createEmployee(…)
vrátí objekt třídyEmployee
, jež je podtřídouPerson
-
Dokonce se skutečný vrácený typ může lišit dle předaných parametrů:
static Person createEmployee(boolean manager)
může dle vrátitManager
neboEmployee
-
Tovární metoda někdy může vracet objekt třídy neznámé v době překladu, tzn. objekt pomocí reflexe dynamicky zavede - tato technika umožňuje modularitu a doplňování kódu a funkcionality dokonce za běhu
U statických továrních metod se držte běžného očekávaného pojmenování, například: newPerson , createPerson , Person.create , pro přístup k skladišti nebo singletonu třeba Person.getInstance() - u get nikdo intuitivně nečeká, že se určitě bude tvořit nový objekt.
|
Namísto složitého konstruktoru použijme vzor "Výrobce"
-
Consider a builder instead of constructor with many parameters
-
Někdy máme složitější objekty s mnoha atributy
-
Konstrukce pak obnáší volat konstruktor s mnoha parametry
-
Příklad
new Person("Jan Novák", true, 22000)
, kdetrue
značí, že jde o muže, a22000
je plat. -
V Javě jsou parametry poziční a nepojmenované (na rozdíl od novějšího Kotlinu např.), je pak problém vidět, co kde nastavujeme -
-
zejména když jsou parametry stejného typu -
new Line(0.0, 0.0, 1.0, 2.0)
znamená přesně co? -
Pro tyto případy se namísto složitého konstruktoru hodí tzv. builder ("budovatel").
-
V moderních prostředích (IDEA, ale i VS Code s pluginy) vidíme, jaký parametr je na které pozici v závorce.
Příklad "budovatel"
public class PersonBuilder {
private String name;
//... male, salary
public void setName(String name) {...}
public void setGender(boolean male) {...}
public void setSalary(double salary) {...}
public Person getPerson() {
// it can perform necessary checks
// and refuse to create unless fulfilled
return new Person(name, male, salary);
}
}
Použití "budovatele"
PersonBuilder builder = new PersonBuilder();
builder.setName("Jan Novák");
builder.setGender(true);
builder.setSalary(22000.0);
// person can immediately be used
Person person = builder.getPerson();
Protipříklad - nepoužití "budovatele"
s - Nemáme budovatele, máme jen cílovou třídu objektů.
public class Person {
private String name;
//... male, salary
public void setName(String name) {
// set name here
}
public void setGender(boolean male) {...}
public void setSalary(double salary) {...}
public Person() {}
}
Bez použití "budovatele"
-
Je to špatně, objekt může bez kontroly zůstat neúplný.
Person person = new Person();
// now the person is NOT complete
// it is dangerous to use, cannot be used
person.setName("Jan Novák");
person.setGender(true);
person.setSalary(22000.0);
// now it is OK
Privátní konstruktory
-
Ve výše uvedených tipech s tovární metodou i výrobcem jsme zamlčeli podstatnou věc:
-
uvedené mechanismy vytvoření objektu se daly obejít jeho přímou konstrukcí
new Person(…)
. -
Aby toto nebylo možné, můžeme všechny konstruktory označit jako
private
-
Pak zvenčí nelze instanci vytvořit pomocí
new Person(…)
, ale musí např. být statická tovární metodaPerson.newInstance()
nebo budovatelBuilder builder = Person.builder()
.
Privátní nebo chráněné konstruktory?
-
Rozdíl mezi
private
aprotected
-
Kdybychom konstruktory označili jako
private
, ale některé přesto ponechaliprotected
, dovolíme tím dědění třídy, např. zPerson
můžeme podědit doEmployee
. -
Kdyby úplně všechny byly
private
, máme smůlu a zPerson
nikdy nic nepodědíme. -
Je to proto, že každá podtřída musí mít konstruktor, který jako první příkaz volá konstruktor předka - a ten kdyby byl soukromý, nebylo by možné.
Singletony
-
Jsou klasickým návrhovým vzorem popsaným jinde.
-
V souvislosti s konstrukcí platí, že objekt singletonu se konstruuje buďto předem nebo až je potřeba
-
Nesmí být zkonstruovatelný jinak, např. přímo přes
new
(konstruktorem) -
Vynucení singletonu pomocí privátních konstruktorů (nebo enum) nebo Prevent instantiation by private constructor - když jsou konstruktory privátní, zvenčí je nelze volat a klient musí využít singleton, třeba
MyClass.getInstance()
nebo tovární metodu.
Příklad singletonu
public class DataManager {
private static DataManager manager;
private DataManager() {
//... initialize data manager
}
public static DataManager getInstance() {
if(manager == null) manager = new DataManager();
return manager;
}
}
Vyhněte se zbytečným objektům
-
Špatně je toto, s každým průchodem je alokován nový řetězec.
-
Nepomohlo by ani
s += String.valueOf(i) + " ";
-
Řešitelné pomocí
StringBuilder
, tam realokace nebude.
String s = "";
for(int i = 0; i < 100; i++) {
// each time a new string is created
// ... and the old forgotten
s = s + String.valueOf(i) + " ";
}
Odstranění zastaralých odkazů na objekty
- statické objekty
-
se dealokují až zcela na konci běhu programu - patří totiž celé třídě a ta se na rozdíl od objektu "nezapomíná"
- vyrovnávací paměti
-
tím, že objekty se v cache pamatují a je to očekávané chování, musíme rozmyslet, kdy už je potřebovat nebudeme a odkazy smazat, nastavit
null
- datové struktury
-
například odkazy z pole jsou pořád "živé", dokud je "živé" celé pole
- zabalení/vybalení z obálky
-
typicky čísla
new Integer(42)
vytvoří nový objekt
Vyhněte se finalizátorům a čističům
-
Finalizér, tzn. metoda
finalize()
mají všechny objekty, -
může teoreticky být překryta a tím umožněn adekvátní "likvidační" postup při zániku objektu - sestávající obvykle z uvolnění systémových zdrojů - síťové sokety, spojení na databázi atd.
-
V Javě nicméně není zaručeno, že se finalizér skutečně zavolá - JVM jej nemusí volat, pokud nepotřebuje fyzicky uvolnit paměť obsazenou již nepoužívaným objektem.
-
I kdyby finalizér zavolán byl, zůstává problém s určením okamžiku, kdy je volán a v jakém pořadí jsou finalizéry na mrtvých objektech volány.
-
Celkově tedy na
finalize()
nespoléhat a nepoužívat je - zdroje uvolňovat explicitním zavoláním vhodné metody.