
Java programátor expert
Množstvo programátorov, hlavne tí, ktorí začali s objektovo-orientovaným programovaním napr. v Jave, má problém pochopiť, načo je nutné používať Encapsulation (zapuzdrenie), keď to vôbec nie je potrebné a akosi to ide a funguje aj bez neho. Mylne sa domnievajú, že v podstate sa jedná len o skrytie dát pred vonkajším svetom. Priznám sa, že som patril do podobnej kategórie ľudí, keď som sa učil pred mnohými rokmi programovať v Jave.
Cieľom tohto článku je si vysvetliť, čo to tá enkapsulácia je, prečo ju začať používať, aké benefity prináša a aký je vzťah medzi zapuzdrením a abstrakciou.
Zapuzdrenie je spôsob spájania členských premenných (teda dát) a správania (metód) do jedného celku, ktorým môže byť trieda, rozhranie, enum a atď. Toto zapuzdrenie zabezpečuje, že údaje nie sú priamo prístupné a rovnaké reštrikcie môžu platiť aj na metódy správania, ktoré s týmito dátami pracujú.
Najlepšie si to ilustrujeme na príklade, kde ide o akúsi kapsulu (podobnú pilulke), ktorá obsahuje dátové metódy a členy a zapuzdrenie predstavuje obal alebo vrstvu, ktorý ich chráni voči vonkajšiemu svetu. Poskytuje však dobre definované verejné rozhranie na prístup a manipuláciu s týmto objektom.
Encapsulation sa tiež stará o to, že do vnútra kapsule nevidíme a teda z pohľadu vonkajšieho pozorovateľa, resp. používateľa nevieme, ako bolo čo implementované.
Encapsulation sa dá predstaviť ako zapuzdrenie metód a členov do kapsule.
V jazyku Java je zapuzdrenie implementované prostredníctvom prístupových modifikátorov, ako sú private, public a protected, ktoré riadia viditeľnosť členov triedy, ako sú polia a metódy. K privátnym členom je možné pristupovať iba v rámci rovnakej triedy, zatiaľ čo k verejným členom je možné pristupovať kdekoľvek v programe. K protected členom je možné pristupovať v rámci rovnakej triedy, podtried a tried v rovnakom balíku.
Pomocou public prístupových get metód dokážeme získať hodnoty uložené v členoch triedy a pomocou set metód ich vieme upraviť a teda vložením novej hodnoty prepísať starú.
Mnohí programátori to však vidia len ako skrývanie dát (data hiding) a sú zmätení prečo jednoducho nedefinujeme každú premennú v triede ako public a ušetríme si písanie kódu pre get a set metódy.
Príklad:
Ak máme triedu Osoba a premennú meno typu String a vek, ktorý je typu int, vedeli by sme to bez zapuzdrenia napísať a používať (zapisovať a čítať hodnoty) takto:
public class Osoba {
public String meno;
public int vek;
}
Osoba osoba = new Osoba();
osoba.meno = "Dominika";
osoba.vek = 25;
System.out.println("Meno: " + osoba.meno);
System.out.println("Vek: " + osoba.vek);
Ak by sme podobnú triedu chceli zapuzdriť, museli by sme skryť dátové členy, to znamená zmeniť prístupové modifikátory napr. na private alebo protected. V takom prípade, ale budeme potrebujeme minimálne get metódy na čítanie, prípadne set metódy na zápis. Celá deklarácia triedy sa nám tak natiahne:
public class Osoba {
private String meno;
private int vek;
public String getMeno() {
return meno;
}
public void setMeno(String meno) {
this.meno = meno;
}
public int getVek() {
return vek;
}
public void setVek(int vek) {
this.vek = vek;
}
}
Osoba osoba = new Osoba();
osoba.setMeno("Dominika");
osoba.setVek(25);
System.out.println("Meno: " + osoba.getMeno());
System.out.println("Vek: " + osoba.getVek());
Takže pri pohľade na množstvo kódu pre jeden a druhý prístup, otázka prečo vlastne nedefinujeme všetko public a zbavíme sa nutnosti definovať get a set metódy, začína dávať zmysel.
Predstavme si, ale že nepozorný, alebo zlomyseľný používateľ zadá ako vek nasledovné:
osoba.vek = -25;
Z logiky veci, vek nemôže byť záporný, ale program o tom nevie. Ten rešpektuje všetky povolené hodnoty rozsahu pre dátový typ int, teda aj záporné hodnoty. Nastavenie dát bez akejkoľvek kontroly potom zvyčajne vedie ku chybám v programe. Pri druhom prístupe však vieme upraviť set metódu tak, že vek musí byť pozitívne číslo.
public void setVek(int vek) {
if (vek >= 0) {
this.vek = vek;
}
}
Podobne by sme si vedeli skontrolovať hodnotu reťazca pre meno, ktoré nám zadal používateľ.
public void setMeno(String meno) {
if(meno == null || meno.equals("")) {
throw new IllegalArgumentException("Meno musi obsahovat aspon 1 znak!");
}
this.meno = meno;
}
Existuje mnoho dobrých dôvodov, prečo zvážiť používanie prístupových metód (accessors), namiesto priameho sprístupnenia polí triedy, poďme sa pozrieť aspoň na niektoré:
Ak chceme správne zapuzdriť našu triedu, neznamená to, že každý dátový člen musí automaticky obsahovať get a set metódu. Tie pridávame podľa potreby. Ak trieda bude vykonávať hlavne funkcionálny kód alebo pracovať s nemennými objektami, bude mať definované iné prístupové metódy ako ak by pracovala so stavovými, meniteľnými objektami.
Aj keď môže začínajúcich programátorov odrádzať písanie viac riadkov kódu určite si treba na zapuzdrenie a použitie prístupových metód zvyknúť. V súčasnosti mnoho programovacích prostredí a editorov ponúka podporu pre generovanie get/set metód (či už v IDE, alebo ako bezplatné doplnky), čo do istej miery znižuje dopad tohto problému.
Ak máme za cieľ písať čisto dátové nemeniteľné triedy, s pomocou Java Records (záznamov), ktorým sme venovali v minulom článku, to ide veľmi jednoducho.
V objektovom svete skrytie dát pred vonkajším svetom nie je zapuzdrenie, ale len jeho nevyhnutná časť. To znamená, že encapsulation nie je len o definovaní prístupových a mutačných metód pre triedu, ale je to širší koncept v objektovo-orientovanom programovaní, ktorý spočíva v minimalizovaní vzájomnej závislosti medzi triedami a obvykle sa realizuje prostredníctvom skrývania informácií. Správne zapuzdrený objekt zapuzdruje stav a algoritmy na manipuláciu s týmto stavom. Dôležité je ale zdôrazniť, že stav má byť zapuzdrený a manipulovaný iba algoritmami poskytnutými samotným objektom.
Krása zapuzdrenia spočíva v schopnosti meniť veci bez toho, aby to ovplyvnilo používateľov, cieľom teda nie je skryť samotné dáta, ale implementačné detaily, ako sa tieto dáta manipulujú.
Hlavnou myšlienkou zapuzdrenia je poskytnúť verejné rozhranie, prostredníctvom ktorého získa používateľ prístup k týmto údajom. Neskôr možno budeme musieť zmeniť vnútornú reprezentáciu údajov bez toho, aby ste ovplyvnili používateľov existujúceho rozhrania.
Ak však sprístupníme samotné dáta, ohrozujeme zapuzdrenie a tým aj schopnosť zmeniť spôsob, akým sa s údajmi manipuluje, vytvoríme závislosť na samotných údajoch a nie na verejnom rozhraní triedy, čím si koledujme skôr či neskôr o veľké problémy.
V Jave môžeme skryť celé triedy, a tým skryť implementačné detaily celého API. Napríklad metóda Arrays.asList() vráti implementáciu rozhrania List, ale nezaujíma nás, ktorá konkrétna implementácia to bude, pokiaľ spĺňa (implementuje) rozhranie List, však? Implementácia môže byť v budúcnosti zmenená bez ovplyvnenia používateľov tejto metódy, používateľ si to však pravdepodobne nevšimne.
Pre lepšie pochopenia konceptu zapuzdrenia je vhodné najprv pochopiť abstrakciu. Ako príklad si vezmime obyčajné auto. Auto sa skladá z množstva komponentov, podobne ako program. Má niekoľko subsystémov, ako sú napr. prevodovka, brzdy, palivový systém atď.
Vieme, že všetky autá majú volant, prostredníctvom ktorého ovládame smer, majú pedál, ktorý keď stlačíme, auto zrýchli a ďalší, ktorý keď stlačíme, zastavíme auto. Ďalej je tu radiaca páka, ktorá nám umožní kontrolovať, či ideme dopredu alebo cúvame. Tieto funkcie tvoria verejné rozhranie abstrakcie auta. So všetkými autami na svete prostredníctvom tohto verejného rozhrania ich abstrakcie zaobchádzame rovnako. Ráno môžeme riadiť jedno auto a potom z neho vystúpiť a popoludní riadiť iné, ako keby to bolo to isté vozidlo.
Len málokto z nás však pozná detaily o tom, ako sú všetky tieto funkcie implementované pod kapotou. Jedného dňa si možno kúpime elektrické auto, ktoré sa zbavilo množstva súčiastok pod kapotou, systém jeho riadenia (teda verejné rozhranie) ostalo rovnaké a preto takmer okamžite s ním budeme vedieť jazdiť. Ak sa však napr. volant nahradí hlasovým ovládaním samo-šoférujúceho auta (zmenili sme zaužívané rozhranie), budeme sa musieť najskôr znovu naučiť ako nové auto používať.
Ako možno na tomto príklade vidieť, cieľom zapuzdrenia je minimalizovať závislosť a uľahčiť zmeny. Maximalizujeme zapuzdrenie tým, že minimalizujeme vystavenie implementačných detailov používateľovi. Stav triedy by mal byť prístupný len prostredníctvom verejného rozhrania.
Bezpečnosť: Zapuzdrenie pomáha zabezpečiť kód, pretože zaisťuje, že iné jednotky (triedy, rozhrania atď.) nebudú mať priamy prístup k údajom.
Flexibilita: Zapuzdrenie robí kód flexibilnejším, čo zase umožňuje programátorovi kód jednoducho meniť alebo aktualizovať.
Kontrola: Zapuzdrenie poskytuje kontrolu nad údajmi uloženými v členských premenných.
Opakovaná použiteľnosť: Zapuzdrený kód alebo jednotku možno opätovne použiť kdekoľvek v aplikácii alebo v celkom iných programoch, bez nutnosti písať kód odznova. Takto fungujú prakticky všetky knižnice.
Udržateľnosť: Zapuzdrené komponenty sa ľahko udržiavajú, pretože veci môžeme ľahko nájsť alebo zmeniť.
Zapuzdrenie je fundamentálny koncept objektovo-orientovaných jazykov, ktorý zahŕňa kombinovanie údajov a metód dohromady a riadenie prístupu k nim. Využíva sa pritom skrývanie údajov, ktoré sa zameriava konkrétne na obmedzenie priameho prístupu k určitým údajom alebo metódam. Rovnako sa využíva vrstva abstrakcie, ktorá ponúka používateľovi verejné rozhranie bez nutnosti poznania vnútorného stavu kapsule – enkapsulácie.
Ak si Java programátor a hľadáš prácu, pozri si naše benefity pre zamestnancov a reaguj na najnovšie ponuky práce.