Objektově orientované programování
Programovací paradigmata |
---|
|
|
Objektově orientované programování (zkracováno na OOP, z anglického Object-oriented programming) je v informatice specifické programovací paradigma, které ho odlišilo od původního imperativního. Kód je v objektovém programování přidružen k datům (metody jsou zapouzdřeny v objektech), což umožňuje snadnější přenos kódu mezi různými projekty (abstrakce a zapouzdření). Propojení umožnilo zavést dědičnost, ale kvůli zjednodušení si vyžádalo zavedení polymorfismu.
Koncepce
- Objekty – jednotlivé prvky modelované reality (jak data, tak související funkčnost) jsou v programu seskupeny do entit, nazývaných objekty. Objekty si pamatují svůj stav a navenek poskytují operace (přístupné jako metody pro volání).
- Abstrakce – programátor, potažmo program, který vytváří, může abstrahovat od některých detailů práce jednotlivých objektů. Každý objekt pracuje jako černá skříňka, která dokáže provádět určené činnosti a komunikovat s okolím, aniž by vyžadovala znalost způsobu, kterým vnitřně pracuje.
- Zapouzdření – zaručuje, že objekt nemůže přímo přistupovat k „vnitřnostem“ jiných objektů, což by mohlo vést k nekonzistenci. Každý objekt navenek zpřístupňuje rozhraní, pomocí kterého (a nijak jinak) se s objektem pracuje, a k tomu se používají: modifikátory přístupu, jmenný prostor...
- Kompozice – Objekt může obsahovat jiné objekty.
- Delegování – Objekt může využívat služeb jiných objektů tak, že je požádá o provedení operace, ty tedy pro okolí vystavují své služby.
- Dědičnost – objekty jsou organizovány stromovým způsobem, kdy objekty nějakého druhu mohou dědit z jiného druhu objektů, čímž přebírají jejich schopnosti, ke kterým pouze přidávají svoje vlastní rozšíření. Tato myšlenka se obvykle implementuje pomocí rozdělení objektů do tříd, přičemž každý objekt je instancí nějaké třídy. Každá třída pak může dědit od jiné třídy (v některých programovacích jazycích i z několika jiných tříd).
- Polymorfismus – odkazovaný objekt se chová podle toho, jaké třídy je instancí. Pozná se tak, že několik objektů poskytuje stejné rozhraní, pracuje se s nimi navenek stejným způsobem, ale jejich konkrétní chování se liší podle implementace. U polymorfismu podmíněného dědičností to znamená, že na místo, kde je očekávána instance nějaké třídy, můžeme dosadit i instanci libovolné její podtřídy, neboť rozhraní třídy je podmnožinou rozhraní podtřídy. U polymorfismu nepodmíněného dědičností je dostačující, jestliže se rozhraní (nebo jejich požadované části) u různých tříd shodují, pak jsou vzájemně polymorfní.
Některé z těchto vlastností jsou pro OOP unikátní, jiné (např. abstrakce) jsou běžnou vlastností i jiných vývojových metodik. OOP je někdy označováno jako programátorské paradigma, neboť popisuje nejen způsob vývoje a zápisu programu, ale i způsob, jakým návrhář programu o problému přemýšlí.
Vztah k principům a fungování reálného světa
Základním paradigmatem OOP je snaha modelovat při řešení úloh principy reálného světa v počítači pokud možno jedna ku jedné. V praktickém životě otevíráme dveře pořád stejně, bez ohledu na to, zda jsou dřevěné nebo laminované, zda mají kukátko, bezpečnostní vložku nebo řetízek navíc. Stejně tak se můžeme dívat na televizi, přepínat programy a docela dobře ji ovládat, přestože nevíme vůbec nic o principech jejího fungování. Analogicky při vývoji složitých informačních systémů mohou vývojáři používat již vytvořené komponenty, podle potřeby si je trochu upravit nebo je používat jako stavebnici pro sestavování důmyslnějších a složitějších objektů.
Programovací jazyky
Existuje velké množství programovacích jazyků umožňujících objektově orientované programování, např. Perl, Smalltalk, Java, C++, Object Pascal, C#, Visual Basic .NET, Progress OpenEdge, Lisp, PHP, Python, Ruby, Go, Rust, D, Swift
Tyto jazyky můžeme rozčlenit do mnoha skupin, jako např.
- Tzv. čistě objektové jazyky, někdy nazývané objektové, ve kterých výpočet probíhá výhradně interakcí objektů, a to vzájemným zasíláním zpráv. Sem patří např. Smalltalk a Ruby.
- Tzv. hybridní jazyky, nebo také objektově orientované, které jsou vytvořeny na imperativním programování a obvykle pouze částečně implementují vlastnosti objektového programování. Mezi takové jazyky patří např. C++, Go, Rust, Swift a D.
- Objektová rozšíření původně neobjektových jazyků, např. Object Pascal, který vznikl jako rozšíření neobjektového jazyka Pascal.
Teorie objektů (object theory)
Kromě vlastních implementací objektů v nějakém programovacím jazyce, existuje i obecná teorie, jež se objekty a jejich vlastnostmi zabývá.
Třída je základním obecným pojmem klasifikace, jak při návrhu uspořádávat informace do smysluplné entity. Základním pojmem je objekt, instance třídy, jako konkrétní případ realizace předpisu. Objekt si „pamatuje“ svůj stav (v podobě dat čili atributů) a zveřejněním některých svých operací (nazývaných metody) poskytuje rozhraní, jak s ním pracovat. Při používání objektu nás zajímá, jaké operace (služby) poskytuje, ale ne, jakým způsobem to provádí – to je princip zapouzdření. Jestli to provádí sám nebo využije služeb jiných objektů, je celkem jedno. Vlastní implementaci pak můžeme změnit (např. zefektivnit), aniž by se to dotklo všech, kteří objekt používají.
Abstrakce objektu, která v architektuře programu podchycuje na obecné úrovni podstatu všech objektů podobného typu, se nazývá třída. Třída je předpis, jak vyrobit objekt daného typu. Například sousedka (chápejme ji jako objekt) má nějaké jméno, je nějak vysoká, umí chodit a umí mluvit. Totéž platí i pro mne. Mohu tedy při modelování těchto dvou objektů, sousedky a mě, abstrahovat od nepodstatných dílčích odlišností a díky této abstrakci vytvořit obecnou třídu Člověk, která bude mít atributy jméno a příjmení (obojí je nějaký řetězec znaků) a metody chodit a mluvit.
Dědičnost
Při vytváření objektů pro chod programu, které budou odpovídat zmíněným dvěma reálným objektům, je třeba vytvořit dvě různé instance třídy Člověk, pro každý modelovaný reálný objekt jednu instanci (tedy jedna instance já a jedna sousedka). Hodnoty atributů jméno a příjmení se pochopitelně v našem případě budou lišit, nicméně obě instance jsou schopny chodit a mluvit díky tomu, že jsme tyto schopnosti obecně přiřadili třídě Člověk.
Představme si dále, že můj soused je svářeč. Stejně jako moje sousedka má jméno, příjmení a umí chodit, mluvit, navíc ovšem umí svářet. Při modelování můžeme využít toho, že svářeč má všechny vlastnosti třídy Člověk a něco navíc. Vytvořme třídu Svářeč jako potomka třídy Člověk. Třída svářeč tímto dědí všechny atributy i metody třídy Člověk (nemusíme je v kódu znovu psát a budeme je upravovat na jediném místě), navíc bude mít metodu svařit.
Metoda svařit může mít například dva parametry (argumenty), prvníKusKovu a druhýKusKovu, a návratovou hodnotu jedenKusKovu. Vím, že když dám sousedovi svářeči dva kusy kovu a požádám o jejich svaření, dostanu je zpátky jako jeden kus kovu a nemusím se starat o to, jak to dokázal. Stejně to bude fungovat v programu. Objekty si mezi sebou mohou posílat zprávy, v našem případě zpráva znamená, že jeden objekt spustí metodu svařit jiného objektu a využije vrácenou hodnotu (odpověď na zprávu). Tomuto způsobu komunikace mezi objekty, která skrývá konkrétní implementaci a stará se jen o zprávy, se v objektově orientovaném programování říká zapouzdření.
Představme si nyní, že potřebujeme vytvořit komplexnější model světa, ve kterém se já od své sousedky začnu lišit. Já umím programovat a ona umí účtovat. Protože nám zůstaly různé společné vlastnosti, ponechme původní třídu Člověk a vytvořme dvě třídy Programátor a Účetní, obě odděděné od třídy Člověk. Nyní však v modelu světa nebude existovat žádná instance třídy Člověk, budou existovat jen instance potomků třídy Člověk, tedy tříd od Člověka odděděných. Pokud skutečně chceme, aby v našem modelu existovali pouze svářeči, programátoři a účetní, ale žádní obecní lidé, kteří by nespadali ani do jedné z těchto skupin, lze označit třídu Člověk jako abstraktní třídu, tedy takovou, od níž nelze tvořit žádné instance.
Metody
Děděním lze nejen rozšiřovat repertoár metod o další, ale i „měnit“ dosavadní zděděné: Například třída Starý_člověk může překrýt (angl. overriding) původní metodu chodit() poděděnou od Člověka svou vlastní implementací a chodit() bude s použitím hole: Sám už tedy jinak než o holi nechodí. Přitom si stále, např. pomocí syntaktické konstrukce zvané tečková konvence, na původní metodu Člověk.chodit() může vzpomenout, má přístup k metodám svých předků.
Jiný způsob, jak upravit nebo rozšířit možnosti (nejen zděděné) je přetěžování metody (angl. method overloading): Například Akrobat je Člověk, který umí nejen normálně chodit() po nohou, ale navíc si doplnil také schopnost volanou chodit(po_rukou). Je na volbě Akrobata, jaký vstup pro svou metodu použije, a podle toho se pak zavolá právě ta jediná odpovídající ze všech přítomných implementací této metody. Rozdíl zde může být buď v počtu předaných parametrů volané metodě, a pak je rozdíl, jestli se vozíme autem jezdit(Vozidlo auto), nebo jestli jedeme autobusem po lince 183 jezdit(Vozidlo autobus, Linka číslo): jde o dvě různé přetížené metody. Anebo zda pro řízení škodovky voláme metodu řídit(škoda) sice se stejným počtem vstupů, ale předáváme různé typy pro metody: řídit(Automobil název) a řídit(Firma název), to jsou dvě různé přetížené metody, obě Člověkovi dostupná zároveň.
Naopak, obě volání jezdit(auto) a jezdit(motorka) použijí stejnou metodu jezdit(Vozidlo vozidlo), nejde o přetížení metody, protože se v obou případech jedná o stejný počet vstupů a i stejný typ vstupu Vozidlo. Ne že by takové ježdění nebyl rozdíl, ale ten už se při takto postavené třídě musí řešit uvnitř metody, rozvětvením běhu programu.
Prvním stupněm řešení je například do objektu zavést dědění: Prohlásit Vozidlo za abstraktní třídu, její metodu jezdit() také za abstraktní, a do odděděných metod jezdit() potomků Automobil a Motocykl přenést jen příslušné větve kódu: Jednou z výhod dědění je právě eliminace složitých podmínek pro větvení.
Jiná možnost je opravdu přetížení metody, kdy se původní obecná metoda jezdit(Vozidlo vozidlo) Člověku ponechá, navíc ale lze dopisovat specifičtější metody jezdit(Autem auto) a jezdit(Na_motorce motorka). Jestli jsou všechny tyto tři metody přítomny už přímo ve třídě Člověk, nebo přibudou další až v odděděné třídě je jedno: řidič je vidí všechny, z jeho pohledu je metoda přetížená, tedy si může vybírat.
Někdy se rodičovská metoda, třeba chodit(), nejen přetěžuje, ale navíc ještě zcela úmyslně překrývá: Třeba zmiňovaný Akrobat se naučil chodit po laně, proto je pro něj žádoucí při volání metody chodit() nejdříve zkusit svou novou schopnost, a až pak použít obecnější chodit() od předka. Nejdříve se ověří, zda Akrobat je_na_laně, podle této podmínky se buď interně zavolá chodit(po_laně), nebo se výslovně zavolá ona zděděná ale právě zakrývaná metoda parent.chodit() od předka.
Akrobat nemusí být odděděn od Člověka ale třeba od Atleta a ten teprve od Člověka. Potom když Akrobat volá metodu předka parent.chodit(), nemusí to znamenat chodit po zemi jako u Člověka, ale třeba chodit po kladině: Metody předků se volají po jednom, zpětně ve sledu dědění, například: Akrobat.chodit() (po laně); když neplatí je_na_laně, zavolá Atlet.chodit() (po žíněnce). Když platí je_na_žíněnce, na volání samotné Člověk.chodit() (po zemi) se nedostane, protože byla splněna jedna z podmínek v překrytých metodách ve sledu dědění.
Obecně se při překrývání může ověřovat nějaká zadaná podmínka, zde prostředí, nebo před zavoláním metody předka zařídit inicializaci, vrácení prostředků systému apod. Podobná konstrukce je podstatná pro destruktor, někdy se používá i s konstruktory.
Rozhraní objektů
Zjistíme, že programátor umí psát na počítači a účetní také. Intuitivně cítíme, že nebudou mít mnoho dalších společných schopností a navíc tuto dovednost může mít napříč povoláními leckdo, proto nemá smysl tvořit třídu na způsob ČlovekPracujícíSPočítačem a od ní dědit Programátora a Účetní, ale je výhodnější například vytvořit rozhraní SchopenPsátNaPočítači
s požadavkem na metodu napišNaPočítači() a upravit třídy Programátor a Účetní tak, aby toto rozhraní implementovaly, tedy předepsanou metodu, a to každý po svém. V definici rozhraní nemůže být obsažen kód (implementace) dané metody, ale všechny třídy, které toto rozhraní implementují, musí být schopny se s příkazem napišNaPočítači() nějak vypořádat.
Obdobně i již zmiňovaná abstraktní třída může předepisovat doimplementování metod, pro které ona sama nemá vlastní kód, ale jen předpis abstraktní metody.
Další důležitou vlastností objektů je polymorfismus. Stejná zpráva může být poslána od různých nebo různým objektům. Lze rozlišit, zda jde o polymorfismus
- nad dvěma různými objekty stejné třídy,
- nad dvěma různými stavy téhož objektu,
- anebo zda zprávu posílají různé objekty různých tříd.
Zapouzdření
Zapouzdření v objektech znamená, že k obsahu objektu se nedostane nikdo jiný, než sám vlastník. Navenek se objekt projeví jen svým rozhraním (operacemi, metodami) a komunikačním protokolem.
Představte si sekretářku a ředitele jako dva objekty. Sekretářka umí rozhraní SchopenPsátNaPočítači
, kde je např. operace napsatDopis
, ale ředitel ho nemá (není to jeho starost). Ale může mít operaci napsatDopis
, protože může požadavek předat jinému objektu – sekretářce. Udržování odkazů na jiné objekty se říká skládání objektů a využívání jejich služeb delegování.
Skládání objektů je jednou z nejdůležitějších vlastností objektového datového modelu. Je o dost důležitější než dědičnost. I dědičnost lze v OOP realizovat pomocí skládání objektů.
Atomizace metod
A konečně zodpovědnost objektu je to, co objekt umí, o co se stará. Objekt by neměl umět sám příliš mnoho a nemusí to dělat sám (viz skládání objektů). Znakem kvalitního návrhu softwaru je to, že třídy v projektu obsahují pokud možno atomické metody, které řeší pouze jeden konkrétní problém.
Související články
Externí odkazy
Obrázky, zvuky či videa k tématu objektově orientované programování na Wikimedia Commons
- Objektově orientované programování (OOP) v PHP
- PHP je věda - Objektově orientované programování nejen v PHP Archivováno 14. 5. 2014 na Wayback Machine.