Lekce 1 - Úvod do objektově orientovaného programování v C#
Vítejte v první lekci úvodu do objektově orientovaného programování v C#. Kurz Základní konstrukce jazyka C# již máme za sebou. V tomto kurzu se naučíme objektově programovat a hlavně objektově myslet. Je to něco trochu jiného, než jsme dělali doteď. Samotný program už nebudeme chápat jako několik řádků příkazů, které interpret vykonává odshora dolů.
Objektově orientované programování (dále jen OOP) nevzniklo náhodou, ale je důsledkem vývoje, který k OOP směřoval. Jedná se o moderní metodiku vývoje softwaru, kterou podporuje většina programovacích jazyků. Lidé se často chybně domnívají, že OOP se využívá pouze při psaní určitého druhu programů a jinak je na škodu. Opak je pravdou – OOP je filozofie, je to nový pohled na funkci programu a komunikaci mezi jeho jednotlivými částmi. OOP by se mělo používat vždy – ať už děláme malou utilitu, nebo složitý databázový systém. OOP není jen technikou nebo nějakou doporučenou strukturou programu, je to hlavně nový způsob myšlení, nový náhled na problémy a nová éra ve vývoji softwaru.
Nejprve se podíváme do historie, jak se programovalo dříve a které konkrétní problémy OOP řeší. Je to totiž důležité k pochopení toho, proč OOP vzniklo.
Evoluce metodik
Mezi tím, jak se programovalo před 40 lety a jak se programuje dnes, je velký rozdíl. První počítače neoplývaly velkým výkonem a ani jejich software nebyl nijak složitý. Vývoj hardwaru je však natolik rychlý, že se počet tranzistorů v mikroprocesorech každý rok zdvojnásobí (Moorův zákon). Lidé se bohužel nedokážou rozvíjet tak rychle, jako se rozvíjí hardware. Stále rychlejší počítače vyžadují stále složitější a složitější software (resp. lidé toho chtějí po počítačích stále více a více). Když se v jedné chvíli zjistilo, že okolo 90 % softwaru je vytvořeno se zpožděním, s dodatečnými náklady nebo vývoj selhává úplně, hledaly se nové cesty, jak programy psát. Vystřídalo se tak několik přístupů, přesněji řečeno paradigmat (rozuměj směrů myšlení). My si je zde popíšeme.
1. Strojový kód
Program byl jen souborem instrukcí, kde jsme neměli žádnou možnost pojmenovávat proměnné nebo zadávat matematické výrazy. Zdrojový kód byl samozřejmě specifický pro daný hardware (procesor). Toto paradigma bylo brzy nahrazeno.
2. Nestrukturované paradigma
Nestrukturovaný přístup je podobný assemblerům. Jedná se o soubor
instrukcí, který se vykonává odshora dolů. Zdrojový kód již nebyl
závislý na hardwaru a byl lépe čitelný pro člověka. Tento přístup na
nějakou dobu umožnil vytvářet komplexnější programy. Bylo tu však stále
mnoho úskalí: jedinou možností, jak udělat něco vícekrát nebo jak se v
kódu větvit, byl příkaz GOTO
. GOTO
nám
umožňovalo "skákat" na jednotlivá místa v programu. Místa byla dříve
specifikována číslem řádku zdrojového kódu, což je samozřejmě
nepraktické. Když do kódu vložíme nový řádek, čísla přestanou
souhlasit a celý kód je rozbitý. Později vznikla možnost definovat tzv.
"návěstí". Takto se obcházela např. absence cyklů. Takovýto způsob
psaní programů je samozřejmě velice nepřehledný a brzy přestal
postačovat pro vývoj složitějších programů.
Uvědomme si, že obrovské rozšíření počítačů za posledních několik desetiletí má za následek růst poptávky po softwaru a logicky také růst poptávky po programátorech. Jistě existují lidé, kteří dokážou bez chyby psát programy v ASM nebo jiných nízkých jazycích, ale kolik jich je? A kolik si asi za takovou nadlidskou práci účtují? Programy je potřeba psát tak, aby i méně zkušení programátoři dokázali psát kvalitní programy a nepotřebovali k tvorbě jednoduché utility 5 let praxe.
3. Strukturované programování
Strukturované programování je první paradigma, které se udrželo delší dobu a opravdu chvíli pro vývoj nových programů postačovalo. Programujeme pomocí cyklů a větvení. To je v podstatě to, kam jsme se doteď dostali.
Program lze rozložit do funkcí (metod), čemuž jsme se nevěnovali, protože to v jazyce C# (který je objektový) ani dost dobře nejde. Raději jsme tento mezikrok přeskočili a začneme rovnou s OOP. U strukturovaného programování hovoříme o tzv. funkcionální dekompozici. Problém se rozloží na několik podproblémů a každý podproblém potom řeší určitá funkce s parametry. Nevýhodou je, že funkce umí jen jednu činnost, a když chceme něco jiného, musíme napsat funkci novou. Neexistuje totiž způsob, jak vzít starý kód a jen trochu ho modifikovat. Musíme psát znovu a znovu, a tak vznikají zbytečné náklady a chyby. Tuto nevýhodu lze částečně obejít pomocí parametrizace funkcí (počet parametrů poté ale rychle narůstá) nebo použitím globálních proměnných. S globálními daty však vzniká nové nebezpečí – funkce mají přístup k datům ostatních. To je začátek konce. Nikdy totiž neuhlídáme, aby někde nedošlo k přepsání glob. dat mezi funkcemi, a začne docházet k nekontrolovatelným problémům. Celý program se potom skládá z nezapouzdřených bloků kódu a špatně se udržuje. Každá úprava programu zvyšuje složitost. Program potom nutně dojde do stavu, kdy náklady na přidávání nových funkcí vzrostou natolik, že se program již nevyplatí rozšiřovat. Zástupci tohoto přístupu jsou například jazyky C a Pascal.
Mezi strukturovaným programováním a objektově orientovaným programováním existoval ještě mezičlánek – tzv. modulární programování. To nám umožňuje zapouzdřit určitou funkcionalitu do modulů. Stále však neexistuje způsob, jak již napsaný kód modifikovat a znovu využít.
Jak jsme již zmínili na začátku článku, někdy se uvádí, že jednoduché programy se mají psát neobjektově, tedy strukturovaně. Není to však pravda. Když opomineme fakt, že porušujeme filozofii OOP jako takovou, nikdy nemůžeme vědět, zda se daný program neuchytí a z malé utility se nestane něco vážnějšího. Potom opět nutně dospějeme do stavu, kdy program nebude možné dál rozšiřovat a budeme ho muset buď zahodit, nebo celý přepsat pomocí OOP.
Neobjektovým metodám psaní kódu se pro jejich nepřehlednost přezdívá "spaghetti code" (protože špagety jsou zamotané).
Objektově orientovaný přístup
Jedná se o filozofii a způsob myšlení, designu a implementace, kdy klademe důraz na znovupoužitelnost. Přístup nalézá inspiraci v průmyslové revoluci – vynález základních komponent, které budeme dále využívat (např. když stavíme dům, nebudeme si pálit cihly a soustružit šroubky, prostě je již máme hotové).
Program poskládaný z komponent je výhodnější a levnější. Také mu můžeme věřit, je otestovaný (o komponentách se ví, že fungují, jsou otestovány a udržovány). Pokud je někde chyba, stačí ji opravit na jednom místě. Jsme motivováni k psaní kódu přehledně a dobře, protože ho po nás používají druzí nebo my sami v dalších projektech (přiznejme si, že člověk je od přírody líný, a kdyby věděl, že se jeho kód nebude již znovu využívat, nesnažil by se ho psát kvalitně ).
Znalosti, které jsme se doteď naučili, samozřejmě budeme používat dál. Náš kód budeme pouze jinak strukturovat, a to do komunikujících objektů.
Jak OOP funguje
Snažíme se nasimulovat realitu tak, jak jsme zvyklí ji vnímat. Můžeme tedy říci, že se odpoutáváme od toho, jak program vidí počítač (stroj), a píšeme program spíše z pohledu programátora (člověka). Podobně jako jsme kdysi nahradili assembler lidsky čitelnými matematickými zápisy, nyní jdeme ještě dál a nahradíme i ty. Jde tedy o určitou úroveň abstrakce nad programem. Takové programování má značné výhody už jen v tom, že je pro nás přirozenější a přehlednější.
Základní jednotkou OOP je objekt, který odpovídá nějakému objektu z reálného světa (např. objekt člověk nebo databáze).
Objekt má své atributy a metody.
Atributy
Atributy jsou vlastnosti neboli data, která objekt
uchovává (např. u člověka to je jmeno
a vek
, u
databáze heslo
). Jedná se o prosté proměnné, s nimiž jsme
již stokrát pracovali. Někdy o nich hovoříme jako o vnitřním stavu
objektu.
Slovo vlastnost si v Microsoftu vyhradili pro specifické využití a totéž platí o atributu. Proměnným objektů říkají field, což se zase kryje s českým slovem pole. Budeme tedy používat slovo atribut, přestože to nebude úplně korektní.
Metody
Metody jsou schopnosti, které umí objekt vykonávat. U
člověka by to mohly být metody: JdiDoPrace()
,
Pozdrav()
nebo Mrkni()
. U databáze
PridejZaznam()
nebo Vyhledej()
. Metody mohou mít
parametry a mohou také vracet nějakou hodnotu. Velmi dobře je již známe.
Používali jsme např. metodu Split()
na objektu
string
. I string
je vlastně objekt, který
reprezentuje v realitě nějaký text. Vidíme, že si můžeme jednoduše
představit, že jednáme s řetězcem textu, něco mu přikazovat nebo na něm
něco nastavovat. Obsahuje metody, které řetězec umí vykonávat
(kopírování, mazání, splitování…), a má také nějaké atributy,
např. Length
, který značí jeho délku.
Ve starších jazycích metody nepatřily objektům, ale volně se nacházely
v modulech (jednotkách). Místo text.Split()
bychom tedy postaru
psali Split(text)
. Nevýhodou samozřejmě bylo zejména to, že
metoda Split()
tehdy nikam nepatřila. Neexistoval způsob, jak si
vyvolat seznam toho, co se s řetězcem dá dělat, a v kódu byl proto
nepořádek. Navíc jsme nemohli mít dvě metody se stejným názvem, v OOP
můžeme mít: uzivatel.Vymaz()
a clanek.Vymaz()
. To
je velmi přehledné a jednoduché. Ve strukturovaném programu bychom však
museli psát: vymaz_uzivatele(uzivatel)
a
vymaz_clanek(clanek)
. Takovýchto hloupých metod bychom museli
mít někde rozházených tisíce. Pokud vám to připomíná jazyk PHP, bohužel máte pravdu. PHP je v tomto opravdu hrozné, a to
z toho důvodu, že jeho návrh je starý. PHP se sice časem plně
přeorientovalo na objekty, ale jeho základy se již nezmění. Jazyk C# je
naštěstí moderní a celý .NET je silně postavený na objektech.
V tomto článku si vysvětlíme jen úplné základy, tedy jak objekty vytvářet a jak zapouzdřit jejich vnitřní logiku. Dalším funkcím OOP (zejména dědičnosti) budou věnovány další lekce, aby toho nebylo moc najednou
Třída
S pojmem třída jsme se již také setkali, chápali jsme ji jako soubor příkazů. Třída však umožňuje mnohem více. Třída je vzor, podle kterého se objekty vytvářejí. Definuje jejich vlastnosti a schopnosti.
Objekt, který se vytvoří podle třídy, se nazývá
instance. Instance mají stejné rozhraní
jako třída, podle které se vytvářejí, ale navzájem se liší svými daty
(atributy). Mějme například třídu Clovek
a od ní si vytvořme
instance karel
a josef
. Obě instance mají jistě
tytéž atributy (např. jmeno
a vek
)a metody
(JdiDoPrace()
a Pozdrav()
), ale hodnoty v nich se
liší. První instance má v atributu jmeno
hodnotu
"Karel"
a v atributu vek
hodnotu 22
, ve
druhém případě jsou hodnoty "Josef"
a 45
.
Komunikace mezi objekty probíhá pomocí předávání zpráv, díky čemuž
je syntaxe přehledná. Zpráva obvykle vypadá takto:
prijemce.NazevMetody(parametry)
. Např. zpráva
karel.Pozdrav(sousedka)
by mohla způsobit, že instance
karel
pozdraví instanci sousedka
.
OOP stojí na třech základních pilířích:
- zapouzdření,
- dědičnost,
- polymorfismus.
Vysvětleme si první z nich.
Zapouzdření
Zapouzdření umožňuje skrýt některé metody a atributy tak, aby zůstaly použitelné jen pro třídu zevnitř. Objekt si můžeme představit jako černou skříňku (anglicky blackbox), která má určité rozhraní (interface), přes které jí předáváme instrukce/data a ona je zpracovává.
Nevíme, jak to uvnitř funguje, ale víme, jak se to navenek chová a používá. Nemůžeme tedy způsobit nějakou chybu, protože využíváme a vidíme jen to, co tvůrce třídy zpřístupnil.
Příkladem může být třída Clovek
, která bude mít atribut
datumNarozeni
a na jeho základě další atributy:
plnolety
a vek
. Kdyby někdo objektu zvenčí atribut
datumNarozeni
změnil, přestaly by platit proměnné
plnolety
a vek
. Říkáme, že vnitřní stav objektu
by byl nekonzistentní. Toto se nám ve strukturovaném programování může
klidně stát. V OOP však objekt zapouzdříme a atribut
datumNarozeni
označíme jako privátní, zvenčí tedy nebude
viditelný. Naopak ven vystavíme metodu ZmenDatumNarozeni()
,
která dosadí nové datum narození do proměnné datumNarozeni
.
Metoda zároveň provede potřebný přepočet věku a přehodnocení
plnoletosti. Použití objektu je bezpečné a aplikace zůstává
stabilní.
Zapouzdření tedy donutí programátory používat objekt jen tím
správným způsobem. Rozhraní (interface) třídy rozdělí na
veřejně přístupné (public
) a vnitřní strukturu
(private
).
V příští lekci, První objektová aplikace v C# - Hello object world, si vytvoříme svůj první objektový program.