Používá se objektově orientované programování. Objektově orientované programování pro začátečníky

Objektově orientované programování (OOP) je v současnosti nejoblíbenější programovací technologií. Objektově orientované programování je vývojem technologie strukturovaného programování, ale má své vlastní charakteristické rysy.

Nejběžnější objektově orientované vizuální programovací systémy jsou Microsoft Visual Basic a Borland Delphi.

Objektově orientované programování je ve svém jádru o vytváření aplikací z objektů, stejně jako se domy staví z bloků a různých částí. Některé objekty je nutné vytvořit zcela vlastními silami, jiné si lze již hotové vypůjčit z různých knihoven.

Důležité místo v technologii objektově orientovaného programování zaujímá událost. Událostmi může být kliknutí myší na objekt, stisknutí určité klávesy, otevření dokumentu atd. Jako reakce na události je volána určitá procedura, která může měnit vlastnosti objektu, volat jeho metody atp.

Objektově orientované programovací systémy obvykle používají grafické rozhraní k vizualizaci procesu programování. Pomocí myši je možné vytvářet objekty, nastavovat jejich vlastnosti a chování.

Objekt má na jedné straně určité vlastnosti, které charakterizují jeho stav v daném časovém okamžiku, a na druhé straně jsou možné operace, které vedou ke změnám vlastností.

Základní jednotkou v objektově orientovaném programování je objekt, který zapouzdřuje jak data, která jej popisují (vlastnosti), tak prostředky pro zpracování těchto dat (metody).

Zapouzdření je spojení vlastností objektu a možných operací (metod) s ním.

Dalším principem OOP je možnost vytvoření nové třídy objektů s dědičností vlastností a metod některé již existující třídy.

Dědičnost je schopnost odvozovat jednu třídu od druhé při zachování všech vlastností a metod třídy předka (progenitor, někdy nazývaný nadtřída) a v případě potřeby přidání nových vlastností a metod.

Sada tříd souvisejících dědičností se nazývá hierarchie. Dědičnost má odrážet takovou vlastnost reálného světa, jako je hierarchie.

Dalším principem OOP je princip polymorfismu.

Polymorfismus je jev, kdy funkce (metoda) se stejným názvem odpovídá různému kódu programu (polymorfnímu kódu) v závislosti na tom, který objekt třídy je použit při volání této metody.

Polymorfismus je zajištěn změnou implementace metody třídy předka v třídě potomka s povinným zachováním signatury metody. Tím je zajištěno, že rozhraní třídy předka zůstane nezměněno a umožní vám přiřadit název metody v kódu k různým třídám – z objektu, jehož třídy je volání provedeno, z této třídy je převzata metoda s daným názvem. Tento mechanismus se nazývá dynamické (nebo pozdní) spojování - na rozdíl od statického (časného) spojování, které se provádí v době kompilace

Objektově orientovaný přístup umožňuje kombinovat statický model, který popisuje vlastnosti objektu, a dynamický model, který popisuje jejich změny.

S tímto přístupem je přístup ke změně vlastností objektu možný pouze prostřednictvím metod patřících k tomuto objektu. Metody „obklopují“ vlastnosti objektu; vlastnosti jsou považovány za "zapouzdřené" v objektu.

V objektově orientovaném programování tak ústřední místo zaujímají objekty, které spojují do jednoho celku (zapouzdřují) vlastnosti objektu a operace (metody) na něm možné.

Objekty, které zapouzdřují stejný seznam vlastností a operací, jsou kombinovány třídy. Každý jednotlivý objekt je instance třídy. Instance třídy mohou mít různé hodnoty vlastností.

Souborový systém počítače může například obsahovat stovky nebo tisíce souborů. Všechny soubory mají stejnou sadu vlastností (název, pozice v systému souborů atd.) a operací (přejmenování, přesun nebo kopírování atd.) a tvoří třídu objektů Soubory.

Každý jednotlivý soubor je instancí této třídy a má specifické hodnoty vlastností (název, umístění atd.).

Často nastává situace, kdy lze stejné operace provádět s objekty různých tříd. Většina tříd objektů v prostředí Windows (složky, dokumenty, symboly atd.) se také vyznačuje sadou stejných operací (přejmenování, přesun, kopírování, mazání atd.). Tato uniformita je uživatelsky velmi přívětivá.

Je však zřejmé, že mechanismy provádění těchto operací nejsou pro různé třídy stejné. Chcete-li například zkopírovat složku, musíte provést posloupnost akcí ke změně systému souborů a ke zkopírování symbolu provést změny v dokumentu. Tyto operace budou provádět různé programy, které jsou k dispozici v operačním systému Windows a v textovém editoru Word.

Tímto způsobem se to realizuje polymorfismus, těch. schopnost provádět stejné operace s objekty patřícími do různých tříd, při zachování individuálních metod pro jejich implementaci pro každou třídu.

Formují se objekty, které mají stejné sady vlastností a metod třída objektu. Takže v aplikaci Word existuje třída objektů dokument(Dokumenty), který má následující vlastnosti: název(Název), umístění(FileNaine) atd. Objekty této třídy mají také určitou sadu metod, například: otevření dokumentupolicajt(OTEVŘENO) tisk dokumentů(Vytisknout), zachovánídokument(Uložit) atd.

Objekty v aplikacích tvoří určitý druh hierarchie. Na vrcholu hierarchie objektů je aplikace(Aplikace). Hierarchie objektů aplikace Word tedy zahrnuje následující objekty: aplikace(Aplikace), dokument(Dokumenty), fragment dokumentu(Výběr), symbol(Postava) atd.

Hierarchie objektů aplikace Excel zahrnuje následující objekty: aplikace(Aplikace), rezervovat(Pracovní sešit), prostěradlo(pracovní list) rozsah buněk(Rozsah), buňkyka(buňka) atd.

Úplný odkaz na objekt se skládá z řady názvů objektů vnořených postupně do sebe. Oddělovače názvů objektů v této řadě jsou tečky; řada začíná objektem aplikace nejvyšší úrovně a končí názvem objektu, který nás zajímá. Například odkaz na dokument Пpo6a.doc ve Wordu bude vypadat takto:

aplikace. Dokumenty("Ppo6a.doc")

Aby objekt mohl provést nějakou operaci, musí být zadána metoda. Mnoho metod má argumenty, které umožňují zadat parametry pro akce, které mají být provedeny. Pro přiřazení konkrétních hodnot argumentům se používá dvojtečka a rovnítko a argumenty jsou odděleny čárkou.

Ve Visual Basic jsou objekty charakterizovány nejen vlastnostmi a metodami, ale také Události. Událost je akce rozpoznaná objektem. Událost může být vygenerována uživatelem (například stisknutím tlačítka myši nebo klávesy na klávesnici) nebo může být výsledkem činnosti jiných objektů aplikace.

Pro každou událost můžete naprogramovat odezvu, tedy reakci objektu na událost, která nastala. Tento program se nazývá událostní procedura. Název procedury události se obvykle skládá z názvu objektu a názvu události. Například pro objekt tlačítka s názvem Příkaz1 a události Klikněte(„klik“, ke kterému dojde, když najedeme kurzorem myši na obrázek tlačítka a stiskneme levé tlačítko myši) procedura události obdrží název Příkaz1_ Klikněte.

Několik objektů se může zúčastnit procedury události. Například ve výše uvedeném postupu Příkaz1_ Klikněte může být přítomen tým

Text1. Text= "Ahoj!",

v důsledku spuštění, které v „textovém poli“ objekt s názvem Text1 Objeví se řádek se slovem „Ahoj!“.

Vizuální programování, jehož metody používá vývojové prostředí Visual Basic, umožňuje vytvářet grafické rozhraní pro vyvíjené aplikace založené na použití ovládacích prvků, mezi které patří tlačítka(Příkazové tlačítko), zaškrtávací políčka(zaškrtávací políčko) , textová pole(Textové pole) combo boxy(ListBox) a další. Tyto objekty, jejich vlastnosti a metody budou diskutovány v další části. Prozatím jen poznamenejme, že ovládací prvky slouží nejčastěji k příjmu dat od uživatele a zobrazení výsledků aplikace. Ovládací prvky jsou tedy základem pro vytváření uživatelského rozhraní aplikace.

Hlavní objekty používané ve vizuálním programování jsou formuláře(Formulář). Formulář je okno, na kterém jsou umístěny ovládací prvky. Formulář je také objekt charakterizovaný souborem vlastností a metod. Stejně jako u jakéhokoli jiného objektu můžete napsat například proceduru události pro formulář Formulář_ Zatížení, která se spouští při výskytu události Load a v jejímž důsledku se provedou instrukce potřebné pro činnost spouštěné aplikace.

Objektově orientované programování (OOP) tvoří základ Javy. V podstatě všechny Java programy jsou do určité míry objektově orientované. Jazyk Java je tak úzce spjat s OOP, že než v něm začnete psát i ty nejjednodušší programy, měli byste se nejprve seznámit se základními principy OOP. Začněme proto úvahou o teoretických otázkách OOP.

Dvě techniky

Všechny počítačové programy se skládají ze dvou prvků: kódu a dat. Navíc může být program koncepčně organizován kolem jeho kódu nebo jeho dat. Jinými slovy, organizace některých programů je určena tím, „co se stane“, zatímco jiné jsou určeny tím, „co je ovlivněno“. Existují dva způsoby vytváření programů. První z nich je tzv procesně orientovaný model a charakterizuje program jako sekvenci lineárních kroků (tj. kód). Na procesně orientovaný model lze nahlížet jako kód, který na data působí. Tento model byl poměrně úspěšně použit v procedurálních jazycích jako C. Ale, jak je uvedeno v kapitole 1, tento přístup přináší řadu obtíží kvůli rostoucí velikosti a složitosti programů.

Za účelem překonání nárůstu složitosti programu byl vyvinut přístup tzv objektově orientované programování. Objektově orientované programování vám umožňuje organizovat program kolem jeho dat (tedy objektů) a sady dobře definovaných rozhraní s těmito daty. Objektově orientovaný program lze charakterizovat jako data, která řídí přístup ke kódu. Jak bude diskutováno později, outsourcing funkcí správy dat může poskytnout několik organizačních výhod.

Abstrakce

Důležitým prvkem OOP je abstrakce. Je lidskou přirozeností reprezentovat složité jevy a předměty tím, že se uchýlí k abstrakci. Lidé si například auto nepředstavují jako sbírku desítek tisíc jednotlivých dílů, ale jako zcela specifický předmět, který má své zvláštní chování. Tato abstrakce vám umožní vyhnout se nutnosti přemýšlet o složitosti dílů, které tvoří auto, když, řekněme, jedete do obchodu. Můžete ignorovat detaily motoru, převodovky a brzdového systému. Místo toho lze objekt použít jako jednu jednotku.

Hierarchické klasifikace jsou účinným prostředkem aplikace abstrakce. To umožňuje zjednodušit sémantiku složitých systémů a rozdělit je na lépe spravovatelné části. Navenek auto vypadá jako jeden objekt. Ale jakmile se podíváte dovnitř, je jasné, že se skládá z několika subsystémů: řízení, brzdy, audio systém, bezpečnostní pásy, topení, navigátor atd. Každý z těchto subsystémů je zase sestaven ze specializovanějších jednotek. Například audio systém se skládá z rádia, CD přehrávače a/nebo audiokazet. Smyslem toho všeho je, že strukturu automobilu (nebo jakéhokoli jiného složitého systému) lze popsat pomocí hierarchických abstrakcí.

Hierarchické abstrakce složitých systémů lze aplikovat i na počítačové programy. Prostřednictvím abstrakce mohou být data tradičního procesně orientovaného programu transformována do jeho základních objektů a sekvence procesních kroků může být transformována do sady zpráv předávaných mezi těmito objekty. Každý z těchto objektů tedy popisuje své vlastní specifické chování. Tyto objekty lze považovat za konkrétní entity, které reagují na zprávy, které je instruují vysvětlit konkrétní akci. To je ve skutečnosti celá podstata OOP.

Principy OOP jsou základem jazyka Java i lidského vnímání světa. Je důležité pochopit, jak jsou tyto principy implementovány v programech. Jak se později ukáže, OOP je další, ale efektivnější a přirozenější technika pro tvorbu programů, které dokážou přežít nevyhnutelné změny, které doprovázejí životní cyklus jakéhokoli velkého softwarového projektu, včetně počátku celkového návrhu, vývoje a zrání. Pokud máte například pečlivě definované objekty a jasná a spolehlivá rozhraní k těmto objektům, můžete bezpečně a snadno odstranit nebo vyměnit části starého systému.

Tři principy OOP

Všechny objektově orientované programovací jazyky poskytují mechanismy pro usnadnění implementace objektově orientovaného modelu. Těmito mechanismy jsou zapouzdření, dědičnost a polymorfismus. Podívejme se na tyto principy OOP jednotlivě.

Zapouzdření

Mechanismus, který spojuje kód a data, s nimiž manipuluje, chrání před vnějšími zásahy a zneužitím zapouzdření. Zapouzdření lze považovat za ochranný shell, který chrání kód a data před náhodným přístupem jiného kódu mimo shell. Přístup ke kódu a datům uvnitř shellu je přísně kontrolován pečlivě definovaným rozhraním. Abychom vytvořili analogii v reálném světě, zvažte automatickou převodovku v autě. Je v něm zapouzdřeno mnoho informací o voze, včetně míry zrychlení, strmosti povrchu, na kterém jede, a polohy řadicí páky. Uživatel (v tomto případě řidič) může toto složité zapouzdření ovlivnit jediným způsobem: pohybem řadicí páky. Převodovka nesmí být ovlivněna například ukazatelem směru nebo stěrači. Řadicí páka je tedy striktně definovaným a v podstatě jediným rozhraním s převodovkou. Navíc to, co se děje uvnitř převodovky, neovlivňuje předměty mimo ni. Například při přeřazení se nerozsvítí světlomety! Funkce automatického řazení je zapouzdřená, takže ji desítky výrobců automobilů mohou implementovat, jak chtějí. Ale z pohledu řidiče fungují všechny tyto převodovky stejně. Podobný princip lze uplatnit v programování. Síla zapouzdřeného kódu spočívá v tom, že každý ví, jak k němu přistupovat, a proto jej lze používat bez ohledu na detaily implementace a bez obav z neočekávaných vedlejších účinků.

Jádrem zapouzdření ejava je třída. Třídy budou podrobněji rozebrány v následujících kapitolách, ale do té doby je užitečné uvést je alespoň stručně. Třída definuje strukturu a chování (data a kód), které budou sdíleny sadou objektů. Každý objekt dané třídy obsahuje strukturu a chování, které jsou definovány třídou, jako by byl objekt „odlit“ do formy třídy. Proto se někdy objekty nazývají instance třídy. Třída je tedy logická konstrukce a objekt je její fyzické ztělesnění.

Když vytváříte třídu, definujete kód a data, která třídu tvoří. Společně se tyto prvky nazývají členů třída. Konkrétně jsou volána data definovaná ve třídě měnit své členy, nebo instance proměnné, a kód, který pracuje s daty, je členské metody nebo jednoduše metody.(Jak říkají programátoři Java metody, Volají programátoři C/C++ funkce) V programech správně napsaných v Javě definují metody, jak se používají členské proměnné. To znamená, že chování a rozhraní třídy jsou určeny metodami, které pracují s daty její instance.

Protože účelem třídy je zapouzdřit složitou programovou strukturu, existují mechanismy pro skrytí složité implementační struktury uvnitř třídy samotné. Každá metoda nebo proměnná ve třídě může být označena jako soukromá nebo veřejná. OTEVŘENO Rozhraní třídy představuje vše, co by externí uživatelé třídy měli nebo mohou znát. ZAVŘENO k metodám a datům lze přistupovat pouze pomocí kódu, který je členem dané třídy. Proto žádný jiný kód, který není členem této třídy, nemůže přistupovat k soukromé metodě nebo proměnné. Soukromí členové třídy jsou přístupní ostatním částem programu pouze prostřednictvím veřejných metod třídy, a tím eliminují možnost provádění nezákonných akcí. To samozřejmě znamená, že veřejné rozhraní musí být pečlivě navrženo a nemělo by odhalovat zbytečné podrobnosti o vnitřním fungování třídy (obrázek 1).

Rýže. 1.

Dědictví

Proces, kterým jeden objekt získává vlastnosti druhého, se nazývá dědictví. Toto je velmi důležitý princip OOP, protože dědičnost poskytuje princip hierarchické klasifikace. Jak již bylo zmíněno dříve, většina znalostí je přístupná k asimilaci prostřednictvím hierarchické (tj. shora dolů) klasifikace. Součástí klasifikace je například zlatý retrívr psi, která zase patří do třídy savci, a ten - do ještě větší třídy zvířat. Bez hierarchií by každý objekt musel explicitně definovat všechny své vlastnosti. Ale díky dědičnosti musí objekt definovat pouze ty věci, které jej ve třídě dělají výjimečným. Objekt může zdědit společné atributy od svého nadřazeného objektu. Mechanismus dědičnosti vám tedy umožňuje udělat z jednoho objektu speciální případ obecnějšího případu. Podívejme se na tento mechanismus podrobněji.

Většina lidí zpravidla vnímá svět kolem sebe ve formě hierarchicky propojených objektů, jako jsou zvířata, savci a psi. Pokud chcete podat abstraktní popis zvířat, můžete říci, že mají určité vlastnosti: velikost, úroveň inteligence a kosterní systém. Zvířata mají také určité charakteristiky chování: jedí, dýchají a spí. Tento popis vlastností a chování tvoří definici třída zvířat.

Pokud bychom měli popsat specifičtější třídu zvířat, jako jsou savci, museli bychom specifikovat specifičtější vlastnosti, jako je typ zubů a mléčných žláz. Tato definice se nazývá podtřída zvířata, která patří supertřída(rodičovská třída) savci. A protože savci jsou jen přesněji definovaná zvířata, oni zdědit všechny vlastnosti zvířat. Podtřída nižší úrovně třídní hierarchie dědí všechny vlastnosti každé své rodičovské třídy (obr. 2).

Rýže. 2.

S opouzdřením souvisí i dědičnost. Pokud jedna třída zapouzdřuje určité vlastnosti, pak každá její podtřída bude mít stejné vlastnosti Plus případné další, které určují jeho specializaci (obr. 3). Kvůli tomuto klíčovému principu se komplexnost objektově orientovaných programů zvyšuje spíše v aritmetickém než geometrickém postupu. Nová podtřída zdědí atributy všech svých nadřazených tříd, a proto nemá nepředvídatelné interakce s většinou zbytku systémového kódu.

Rýže. 3. Labradorit plně dědí zapouzdřené vlastnosti všech rodičovských tříd zvířat

Polymorfismus

Polymorfismus(z řeckého „mnoho forem“) je princip OOP, který umožňuje použití stejného rozhraní pro společnou třídu akcí. Každá akce závisí na konkrétní situaci. Vezměme si jako příklad zásobník, který funguje jako reverzní seznam úložiště. Řekněme, že program vyžaduje tři typy zásobníků: pro celočíselné hodnoty, pro číselné hodnoty s plovoucí desetinnou čárkou a pro znaky. Algoritmus implementace pro každý z těchto zásobníků zůstává stejný, navzdory rozdílům v datech, která jsou v nich uložena. V neobjektově orientovaném jazyce by manipulace se zásobníkem vyžadovala vytvoření tří různých sad utilit pod samostatnými názvy. V Javě lze díky principu polymorfismu definovat společnou sadu utilit pro manipulaci se zásobníkem pod stejnými společnými názvy.

Obecněji je princip polymorfismu často vyjádřen frází „jedno rozhraní, více metod“. To znamená, že je možné vyvinout společné rozhraní pro skupinu činností, které spolu souvisí. Tento přístup snižuje složitost programu, protože ke specifikaci se používá stejné rozhraní obecná skupina akcí. A výběr konkrétní akci(tj. metoda) se provádí ve vztahu ke každé situaci a je odpovědností kompilátoru. To ušetří programátorovi nutnost provádět tyto volby ručně. Stačí si zapamatovat obecné rozhraní a správně jej aplikovat.

Pokud budeme pokračovat v analogii se psy, můžeme říci, že psí čich je polymorfní vlastnost. Pokud pes ucítí kočku, bude štěkat a pronásledovat ji. A pokud pes ucítí jeho jídlo, začne slinit a vrhne se ke své misce. V obou případech funguje stejný čich. Rozdíl je pouze v tom, co přesně vůni vydává, tzn. v datovém typu ovlivňujícím nos psa! Tento obecný princip lze implementovat jeho aplikací na metody v programu Java.

Kombinované použití polymorfismu, zapouzdření a dědičnosti

Když jsou principy polymorfismu, zapouzdření a dědičnosti aplikovány správně, tvoří společně programovací prostředí, které podporuje vývoj programů, které jsou robustnější a škálovatelnější, než by tomu bylo u procesně orientovaného modelu. Pečlivě navržená hierarchie tříd poskytuje pevný základ pro opětovné použití kódu, jehož vývojem a testováním jste strávili čas a úsilí. Zapouzdření umožňuje vrátit se k dříve vytvořeným implementacím bez porušení kódu, který závisí na veřejném rozhraní tříd používaných v aplikaci. A polymorfismus umožňuje vytvářet jasný, praktický, čitelný a stabilní kód.

Ze dvou reálných příkladů uvedených dříve, příklad automobilů plněji ilustruje schopnosti OOP. Zatímco příklad psů je pro zvažování OOP z hlediska dědictví docela vhodný, příklad aut má s programy společného více. Při řízení různých typů (podtříd) vozů využívají všichni řidiči dědičnost. Ať už se jedná o školní autobus, osobní automobil, sportovní vůz nebo rodinný minivan, všichni řidiči budou moci snadno najít a ovládat volant, brzdy a plynový pedál. S trochou šťouchání s řadicí pákou většina lidí dokonce dokáže ocenit rozdíly mezi manuální převodovkou a automatem, pokud mají jasnou představu o společné mateřské třídě těchto dvou, převodovém systému.

Při používání automobilů lidé neustále interagují s jejich zapouzdřenými vlastnostmi. Brzdový a plynový pedál skrývají neuvěřitelnou složitost odpovídajících objektů za rozhraním tak jednoduchým, že k ovládání těchto objektů stačí sešlápnout pedál nohou! Specifická implementace motoru, typ brzdy a velikost pneumatiky nemají žádný vliv na to, jak interagují s určením třídy pedálů.

Konečně polymorfismus jasně odráží schopnost výrobců automobilů nabízet širokou škálu možností pro v podstatě stejné vozidlo. Vozidlo může být například vybaveno protiblokovacími brzdami nebo tradičními brzdami, posilovačem řízení nebo hřebenovým řízením a 4-, 6- nebo 8válcovými motory. Ale v každém případě budete muset sešlápnout brzdový pedál, abyste zastavili, otočit volantem, abyste zatočili, a sešlápnout plynový pedál, aby se auto rozjelo rychleji. Stejné rozhraní lze použít k ovládání široké škály implementací.

Jak vidíte, díky kombinované aplikaci principů zapouzdření, dědičnosti a polymorfismu lze jednotlivé díly proměnit v objekt zvaný auto. Totéž platí pro počítačové programy. Principy OOP umožňují sestavit koherentní, spolehlivý a udržovatelný program z mnoha samostatných částí.

Jak bylo uvedeno na začátku této části, každý program Java je objektově orientovaný. Přesněji řečeno, každý program Hajava uplatňuje principy zapouzdření, dědičnosti a polymorfismu. Na první pohled se může zdát, že ne všechny tyto principy se vyskytují v krátkých ukázkových programech uvedených ve zbytku této kapitoly a v řadě následujících kapitol, ale přesto jsou v nich přítomny. Jak bude zřejmé později, mnoho funkcí jazyka Java je součástí vestavěných knihoven tříd, které široce využívají principy zapouzdření, dědičnosti a polymorfismu.

Pravděpodobně polovina volných míst (ne-li více) vyžaduje znalost a porozumění OOP. Ano, tato metodika rozhodně uchvátila mnoho programátorů! Pochopení OOP obvykle přichází se zkušenostmi, protože na toto téma prakticky neexistují žádné vhodné a dostupné materiály. A i kdyby, není zdaleka jisté, že na ně čtenáři narazí. Doufám, že se mi podaří vysvětlit principy této nádherné metodiky, jak se říká, na prstech.

Již na začátku článku jsem tedy již zmínil pojem „metodika“. Při aplikaci na programování tento termín implikuje přítomnost jakékoli sady způsobů, jak organizovat kód, metody jeho psaní, podle kterých bude programátor schopen psát zcela použitelné programy.

OOP (neboli objektově orientované programování) je způsob organizace programového kódu, kde hlavními stavebními kameny programu jsou objekty a třídy a logika programu je postavena na jejich interakci.


O objektech a třídách

Třída- jedná se o datovou strukturu, kterou si může vytvořit sám programátor. Z hlediska OOP se třída skládá z pole(zjednodušeně řečeno - proměnné) a metody(zjednodušeně řečeno – funkce). A jak se ukázalo, spojení dat a funkcí pro práci s nimi v jedné struktuře dává nepředstavitelnou sílu. Objekt je konkrétní instancí třídy. Podle analogie třídy s datovou strukturou je objekt specifická datová struktura, která má ke svým polím přiřazené nějaké hodnoty. Dovolte mi to vysvětlit na příkladu:

Řekněme, že potřebujeme napsat program, který vypočítá obvod a plochu trojúhelníku, která je dána dvěma stranami a úhlem mezi nimi. K napsání takového programu pomocí OOP budeme potřebovat vytvořit třídu (tedy strukturu) Triangle. Třída Triangle uloží tři pole (tři proměnné): strana A, strana B, úhel mezi nimi; a dvě metody (dvě funkce): vypočítat obvod, vypočítat plochu. Pomocí této třídy můžeme popsat libovolný trojúhelník a vypočítat obvod a plochu. Takže konkrétní trojúhelník se specifickými stranami a úhlem mezi nimi se bude nazývat instancí třídy Triangle. Třída je tedy šablona a instance je konkrétní implementace šablony. Ale instance jsou objekty, tedy specifické prvky, které uchovávají konkrétní hodnoty.

Jedním z nejběžnějších objektově orientovaných programovacích jazyků je java. Tam se bez použití předmětů prostě neobejdete. Takto by vypadal kód třídy popisující trojúhelník v tomto jazyce:

/** * Třída trojúhelník. */ class Triangle ( /** * Speciální metoda zvaná konstruktor třídy. * Jako vstup bere tři parametry: * délka strany A, délka strany B, * úhel mezi těmito stranami (ve stupních) */ Triangle(double sideA, double stranaB , dvojitý úhelAB) ( tato.stranaA = stranaA; tato.stranaB = stranaB; tento.uhelAB = úhelAB; ) dvojitá stranaA; //pole třídy, uloží hodnotu strany A do popsaného trojúhelníku dvojitá stranaB; //pole třídy , ukládá hodnotu strany B do popsaného trojúhelníku double angleAB; //Pole třídy ukládá úhel (ve stupních) mezi dvěma stranami v popsaném trojúhelníku /** * Metoda třídy, která počítá obsah trojúhelníku */ double getSquare() ( double square = this.sideA * this .sideB * Math.sin(this.angleAB * Math.PI / 180); return square; ) /** * Metoda třídy, která vypočítá obvod trojúhelníku */ double getPerimeter() ( double sideC = Math.sqrt(Math.pow (this.sideA, 2) + Math.pow(this.sideB, 2) - 2 * this.sideA * this.sideB * Math.cos(this. angleAB * Math.PI / 180)); dvojitý obvod = tato.stranaA + tato.stranaB + stranaC; návratový obvod; ))

Pokud do třídy přidáme následující kód:

/** * Zde se program spustí */ public static void main(String args) ( //Hodnoty ​​5, 17, 35 přejdou do konstruktoru třídy Triangle Triangle trojúhelník1 = new Triangle(5, 17, 35 ); System.out .println("Oblast trojúhelníku1: "+trojúhelník1.getSquare()); System.out.println("Obvod trojúhelníku1: "+trojúhelník1.getPerimeter()); //Hodnoty 6 , 8, 60 přejděte na konstruktor třídy Triangle Triangle trojúhelník2 = new Triangle(6, 8, 60); System.out.println("Oblast trojúhelníku1: "+triangle2.getSquare()); System.out.println ("Obvod trojúhelníku1: "+trojúhelník2.getPerimeter()); )

pak již může být program spuštěn ke spuštění. Toto je vlastnost jazyka Java. Pokud má třída takovou metodu

Veřejné statické void main (string args)

pak lze tuto třídu spustit. Podívejme se na kód podrobněji. Začněme čárou

Trojúhelník trojúhelník1 = nový Trojúhelník(5, 17, 35);

Zde vytvoříme instanci trojúhelníku1 třídy Triangle a hned jí přidělíme parametry stran a úhel mezi nimi. Současně se zavolá speciální metoda zvaná konstruktor a vyplní pole objektu hodnotami předanými konstruktoru. No a co ty linky?

System.out.println("Oblast trojúhelníku1: "+trojúhelník1.getSquare()); System.out.println("Obvod trojúhelníku1: "+trojúhelník1.getPerimetr());

odešlete vypočítanou plochu trojúhelníku a jeho obvod na konzolu.

Totéž se stane pro druhou instanci třídy Triangle.

Pochopení podstaty tříd a konstrukce konkrétních objektů je sebevědomým prvním krokem k pochopení metodologie OOP.

Ještě jednou to nejdůležitější:

OOP- toto je způsob organizace programového kódu;

Třída- jedná se o vlastní datovou strukturu, která sdružuje data a funkce pro práci s nimi (pole tříd a metody tříd);

Objekt je konkrétní instancí třídy, jejíž pole mají specifické hodnoty.


Tři kouzelná slova

OOP zahrnuje tři klíčové přístupy: dědičnost, zapouzdření a polymorfismus. Pro začátek uvedu definice z wikipedie:

Encapsulation je systémová vlastnost, která umožňuje kombinovat data a metody, které s nimi pracují, ve třídě. Některé jazyky (např. C++) přirovnávají zapouzdření ke skrytí, ale většina (Smalltalk, Eiffel, OCaml) tyto pojmy rozlišuje.

Dědičnost je systémová vlastnost, která vám umožňuje popsat novou třídu na základě existující třídy s částečně nebo zcela vypůjčenou funkčností. Třída, ze které je odvozena dědičnost, se nazývá základní, rodičovská nebo nadtřída. Nová třída je potomek, dědic, potomek nebo odvozená třída.

Polymorfismus je systémová vlastnost, která umožňuje používat objekty se stejným rozhraním bez informací o typu a vnitřní struktuře objektu.

Pochopit, co všechny tyto definice vlastně znamenají, je poměrně obtížné. Ve specializovaných knihách, které toto téma pokrývají, je každé definici často věnována celá kapitola, minimálně však odstavec. I když podstata toho, co potřebuje programátor pochopit a navždy vtisknout do svého mozku, je velmi malá.
A jako příklad pro analýzu použijeme postavy na rovině. Ze školní geometrie víme, že u všech obrazců popsaných na rovině je možné vypočítat obvod a plochu. Například pro bod jsou oba parametry rovny nule. U segmentu můžeme vypočítat pouze obvod. A pro čtverec, obdélník nebo trojúhelník - obojí. Nyní popíšeme tento úkol v podmínkách OOP. Je také dobré pochopit řetězec uvažování, který vede k hierarchii tříd, která je zase ztělesněna v pracovním kódu. Jít:


Bod je tedy nejmenší geometrický útvar, který je základem všech ostatních konstrukcí (obrázků). Proto byl bod vybrán jako základní rodičovská třída. Pojďme napsat bodovou třídu v Javě:

/** * Bodová třída. Základní třída */ class Point ( /** * Prázdný konstruktor */ Point() () /** * Metoda třídy, která vypočítá plochu obrázku */ double getSquare() ( return 0; ) /** * Metoda třídy, která vypočítá obvod obrázku */ double getPerimeter() ( return 0; ) /** * Metoda třídy, která vrátí popis obrázku */ String getDescription() ( return "Point"; ) )

Výsledná třída Point má prázdný konstruktor, protože v tomto příkladu pracujeme bez konkrétních souřadnic a pracujeme pouze s parametry a bočními hodnotami. Protože bod nemá žádné strany, není třeba mu předávat žádné parametry. Všimněte si také, že třída má metody Point::getSquare() a Point::getPerimeter() pro výpočet plochy a obvodu, obě vrací 0. Pro bod je to logické.


Protože náš bod je základem všech ostatních obrazců, dědíme třídy těchto dalších obrazců ze třídy Point. Popišme třídu segmentu zděděnou z třídy bodu:

/** * Class Line Segment */ class LineSegment extends Point ( LineSegment(double segmentLength) ( this.segmentLength = segmentLength; ) double segmentLength; // Délka čáry /** * Přepsaná metoda třídy, která vypočítává plochu řádek */ double getSquare( ) ( return 0; ) /** * Přepsaná metoda třídy, která vypočítá obvod segmentu */ double getPerimeter() ( return this.segmentLength; ) String getDescription() ( return "Délka segmentu: " + this.segmentLength; ) )

Třída LineSegment rozšiřuje Point

znamená, že třída LineSegment dědí z třídy Point. Metody LineSegment::getSquare() a LineSegment::getPerimeter() přepisují odpovídající metody základní třídy. Plocha segmentu je vždy nula a plocha obvodu se rovná délce tohoto segmentu.

Nyní, stejně jako třídu segmentů, popíšeme třídu trojúhelníku (která také dědí z třídy bodů):

/** * Třída trojúhelník. */ class Triangle extends Point ( /** * Konstruktor třídy. Jako vstup bere tři parametry: * délka strany A, délka strany B, * úhel mezi těmito stranami (ve stupních) */ Triangle(double sideA, double sideB, double angleAB ) ( this.sideA = sideA; this.sideB = sideB; this.angleAB = angleAB; ) double sideA; //Pole třídy, uloží hodnotu strany A do popsaného trojúhelníku double sideB; //Pole třídy, uloží hodnota strany B v popsaném trojúhelníku trojúhelník double angleAB; //Pole třídy, které ukládá úhel (ve stupních) mezi dvěma stranami v popsaném trojúhelníku /** * Metoda třídy, která vypočítá obsah trojúhelníku */ double getSquare() ( double square = (this.sideA * this.sideB * Math.sin(this.angleAB * Math.PI / 180)))/2; return square; ) /** * Metoda třídy, která počítá obvod a trojúhelník */ double getPerimeter() ( double sideC = Math.sqrt(Math. pow(this.sideA, 2) + Math.pow(this.sideB, 2) - 2 * this.sideA * this.sideB * Math.cos (tento.uhelAB * Math.PI / 180)); dvojitý obvod = tato.stranaA + tato.stranaB + stranaC; návratový obvod; ) String getDescription() ( return "Trojúhelník se stranami: " + this.sideA + ", " + this.sideB + " a úhel mezi nimi: " + this.angleAB; ) )

Není tu nic nového. Také metody Triangle::getSquare() a Triangle::getPerimeter() přepisují odpovídající metody základní třídy.
No a teď ve skutečnosti samotný kód, který ukazuje kouzlo polymorfismu a odhaluje sílu OOP:

Class Main ( /** * Zde se program spouští */ public static void main(String args) ( //ArrayList - Toto je speciální datová struktura v jazyce Java //, která vám umožňuje ukládat objekty určitého typu do ArrayList figure = new ArrayList (); //přidání tří různých objektů k figurám pole figure.add(new Point()); Figure.add(new LineSegment(133)); Figure.add(new Triangle(10, 17, 55)); pro (int i = 0; i

Vytvořili jsme pole objektů třídy Point, a protože třídy LineSegment a Triangle dědí od třídy Point, můžeme je umístit do tohoto pole. Ukazuje se, že každou postavu, která je v poli figurek, můžeme považovat za objekt třídy Point. Toto je polymorfismus: není známo, do které třídy patří objekty v poli figurek, ale protože všechny objekty v tomto poli patří do stejné základní třídy Point, pak jsou také použitelné všechny metody, které jsou použitelné pro třídu Point. do tříd jeho potomků.


Nyní o zapouzdření. Skutečnost, že jsme umístili parametry obrazce a metody pro výpočet plochy a obvodu do jedné třídy, je zapouzdření, obrazce jsme zapouzdřili do samostatných tříd. To, že pro výpočet perimetru ve třídě používáme speciální metodu, je zapouzdření, výpočet obvodu jsme zapouzdřili v metodě getPerimiter(). Jinými slovy, zapouzdření skrývá implementaci (možná nejkratší a zároveň prostornou definici zapouzdření).


Celý příklad kódu:

Importovat java.util.ArrayList; class Main ( /** * Zde se program spouští */ public static void main(String args) ( //ArrayList je speciální datová struktura v jazyce Java //, která umožňuje ukládat objekty určitého typu do pole. ArrayList figure = new ArrayList (); //přidání tří různých objektů k figurám pole figure.add(new Point()); Figure.add(new LineSegment(133)); Figure.add(new Triangle(10, 17, 55)); pro (int i = 0; i

v podstatě využíval paradigma preskriptivního programování – cílem bylo vytvořit kód, který vhodně působí na data. Tento přístup je dobrý pro řešení malých problémů, ale při pokusu o vytvoření vytváří mnoho neřešitelných problémů velké softwarové systémy.

Jedna z alternativ direktivní programování je objektově orientované programování, který opravdu pomáhá vyrovnat se s nelineárně rostoucí složitostí programů s rostoucím objemem. Neměli bychom však dospět k závěru, že použití paradigmatu objektově orientovaného programování zaručuje úspěšné řešení všech problémů.

Abyste se stali profesionálem v programování, potřebujete talent, kreativitu, inteligenci, znalosti, logiku, schopnost budovat a používat abstrakce a hlavně zkušenosti.

V této části budeme pokračovat v úvodu do základních pojmů objektově orientovaného programování, který jsme začali v první kapitole knihy. Nejprve budou probrány koncepty OOP společné pro různé programovací jazyky a poté jejich implementace v jazyce Java.

Měli byste si být vědomi toho, že kurz objektově orientovaného programování je vyučován vysokoškolským studentům v průběhu celého semestru, a proto níže uvedený materiál představuje pouze velmi základní úvod do světa OOP. Mnohem úplnější zpracování mnoha problémů souvisejících s objektově orientovaným návrhem, inženýrstvím a programováním je obsaženo v knize a ve třetí kapitole knihy můžete najít velmi jasný popis všech objektově orientovaných aspektů jazyk Java.

Základní koncepty OOP

Objektově orientované programování nebo OOP (objektově orientované programování) - metodika programování založené na reprezentaci programu jako kolekce objektů, z nichž každý je implementací určitého typu, pomocí mechanismu přeposílání zpráv a třídy organizované v dědičná hierarchie.

Ústředním prvkem OOP je abstrakce. Data jsou převedena na objekty pomocí abstrakce a sekvence zpracování těchto dat se změní na sadu zpráv předávaných mezi těmito objekty. Každý z objektů má své vlastní jedinečné chování. S objekty lze zacházet jako s konkrétními entitami, které reagují na zprávy, které jim přikazují provést nějakou akci.

OOP se vyznačuje následujícími principy (podle Alana Kaye):

  • vše je předmět;
  • výpočty se provádějí prostřednictvím interakce (výměny dat) mezi objekty, kdy jeden objekt vyžaduje, aby jiný objekt provedl nějakou akci; objekty interagují odesíláním a přijímáním zpráv; zpráva je požadavek na provedení akce doplněný o sadu argumentů, které mohou být potřebné při provádění akce;
  • každý objekt má nezávislou Paměť, který se skládá z dalších objektů;
  • každý objekt je zástupcem třídy, která vyjadřuje obecné vlastnosti objektů daného typu;
  • nastavit ve třídě funkčnost(chování objektu); tedy všechny objekty, které jsou instancemi stejné třídy, mohou provádět stejné akce;
  • třídy jsou organizovány do jediné stromové struktury se společným kořenem, tzv dědičná hierarchie; paměť a chování spojené s instancemi konkrétní třídy jsou automaticky dostupné jakékoli třídě níže v hierarchickém stromu.

Definice 10.1. Abstrakce- způsob řešení problému, při kterém jsou objekty různého druhu sjednoceny společným konceptem (konceptem), a poté jsou seskupené entity považovány za prvky jediné kategorie.

Abstrakce umožňuje oddělit logický význam fragmentu programu od problému jeho implementace, dělení vnější popis(rozhraní) objektu a jeho vnitřní organizace(implementace).

Definice 10.2. Zapouzdření- technika, ve které se v něm skrývají informace, které jsou z hlediska rozhraní objektu nevýznamné.

Definice 10.3. Dědictví- vlastnost objektů, jejichž prostřednictvím instance třídy získávají přístup k datům a metodám tříd předků, aniž by je předefinovaly.

Dědičnost umožňuje různým datovým typům sdílet stejný kód, což má za následek menší kód a větší funkčnost.

Definice 10.4.

Nevím, jak programovat v objektově orientovaných jazycích. Neučil jsem se. Po 5 letech průmyslového programování v Javě stále nevím, jak vytvořit dobrý systém v objektově orientovaném stylu. prostě nechápu.

Snažil jsem se učit, upřímně. Studoval jsem vzory, četl kód open source projektů, zkoušel jsem si v hlavě budovat ucelené koncepty, ale stále jsem nepochopil principy tvorby kvalitních objektově orientovaných programů. Možná jim rozuměl někdo jiný, ale já ne.

A tady je pár věcí, které mě pletou.

Nevím, co je OOP

Vážně. Je pro mě těžké formulovat hlavní myšlenky OOP. Ve funkcionálním programování je jednou z hlavních myšlenek bezstavová. Ve strukturálním - rozkladu. V modulárním provedení je funkčnost rozdělena do celých bloků. V každém z těchto paradigmat platí dominantní principy pro 95 % kódu a jazyk je navržen tak, aby podporoval jejich používání. Neznám žádná taková pravidla pro OOP.
  • Abstrakce
  • Zapouzdření
  • Dědictví
  • Polymorfismus
Vypadá to jako soubor pravidel, že? To jsou tedy pravidla, která je potřeba dodržovat v 95 % případů? Hmm, pojďme se na to podívat blíže.

Abstrakce

Abstrakce je mocný programovací nástroj. To nám umožňuje budovat velké systémy a udržovat nad nimi kontrolu. Je nepravděpodobné, že bychom se kdy přiblížili dnešní úrovni programů, kdybychom nebyli vyzbrojeni takovým nástrojem. Jak však souvisí abstrakce s OOP?

Za prvé, abstrakce není atributem výhradně OOP nebo programování obecně. Proces vytváření úrovní abstrakce zasahuje téměř do všech oblastí lidského poznání. O materiálech tak můžeme soudit, aniž bychom zacházeli do detailů jejich molekulární struktury. Nebo mluvit o předmětech, aniž byste zmínili materiály, ze kterých jsou vyrobeny. Nebo mluvit o složitých mechanismech, jako je počítač, turbína letadla nebo lidské tělo, aniž bychom si pamatovali jednotlivé detaily těchto entit.

Za druhé, v programování vždy existovaly abstrakce, počínaje spisy Ady Lovelace, která je považována za první programátorku v historii. Od té doby lidé neustále vytvářejí abstrakce ve svých programech, často pouze s těmi nejjednoduššími nástroji. Abelson a Sussman tedy ve své známé knize popisují, jak vytvořit systém pro řešení rovnic, který podporuje komplexní čísla a dokonce i polynomy, pouze pomocí procedur a spojených seznamů. Jaké další prostředky abstrakce tedy OOP poskytuje? Nemám ponětí. Rozdělení kódu do podprogramů? To dokáže jakýkoli jazyk na vysoké úrovni. Kombinovat rutiny na jednom místě? Na to je dostatek modulů. Typizace? Existovala dávno před OOP. Příklad se systémem pro řešení rovnic jasně ukazuje, že konstrukce úrovní abstrakce nezávisí ani tak na jazykových nástrojích, ale na schopnostech programátora.

Zapouzdření

Hlavní výhodou zapouzdření je skrytí implementace. Klientský kód vidí pouze rozhraní a může se na něj spolehnout. To uvolní vývojářům, kteří se mohou rozhodnout implementaci změnit. A to je opravdu skvělé. Otázkou ale opět je, co s tím má společného OOP? Všechno Výše uvedená paradigmata zahrnují skrytí implementace. Při programování v C alokujete rozhraní do hlavičkových souborů, Oberon vám umožňuje vytvořit pole a metody lokální pro modul a konečně abstrakce v mnoha jazycích je postavena jednoduše pomocí podprogramů, které také zapouzdřují implementaci. Objektově orientované jazyky jsou navíc samy o sobě často porušují pravidlo zapouzdření, poskytování přístupu k datům prostřednictvím speciálních metod - getterů a setterů v Javě, vlastností v C# atp. (V komentářích jsme zjistili, že některé objekty v programovacích jazycích nejsou objekty z pohledu OOP: objekty přenosu dat jsou zodpovědné pouze za přenos dat, a nejsou tedy plnohodnotnými OOP entitami, a proto neexistuje potřebují, aby zachovaly zapouzdření. Na druhou stranu, metody přístupového objektu jsou nejlépe zachovány, aby byla zachována architektonická flexibilita. Takto se to komplikuje.) Navíc některé objektově orientované jazyky, jako je Python, se nesnaží vůbec nic skrývat , ale spolehněte se pouze na inteligenci vývojářů používajících tento kód.

Dědictví

Dědičnost je jednou z mála novinek, které díky OOP skutečně vstoupily na scénu. Ne, objektově orientované jazyky nevytvořily novou myšlenku - dědičnost lze implementovat v jakémkoli jiném paradigmatu - ale OOP poprvé přinesl tento koncept na úroveň jazyka samotného. Výhody dědičnosti jsou také zřejmé: když si téměř spokojeni s nějakou třídou, můžete vytvořit potomka a přepsat nějakou část jeho funkčnosti. V jazycích, které podporují vícenásobnou dědičnost, jako je C++ nebo Scala (v druhém případě prostřednictvím vlastností), se objevuje další případ použití - mixiny, malé třídy, které umožňují „namíchat“ funkce do nové třídy bez kopírování kódu.

Takže to je to, co odlišuje OOP jako paradigma od ostatních? Hmm... pokud ano, proč to tak zřídka používáme ve skutečném kódu? Pamatujete si, co jsem řekl o 95 % kódu, který se řídí pravidly dominantního paradigmatu? Nedělal jsem si srandu. Ve funkčním programování minimálně 95 % kódu využívá neměnná data a funkce bez vedlejších efektů. V modulárním systému je téměř veškerý kód logicky zabalen do modulů. Zastánci strukturovaného programování se podle Dijkstrových předpisů snaží rozdělit všechny části programu na malé části. Dědičnost se používá mnohem méně často. Možná v 10% kódu, možná v 50%, v některých případech (například při dědění z rámcových tříd) - v 70%, ale ne více. Protože ve většině situací je to snadné není třeba.

Navíc dědictví nebezpečný za dobrý design. Tak nebezpečné, že Gang čtyř (zdánlivě kazatelé OOP) ve své knize doporučuje nahradit to delegováním, kdykoli je to možné. Dědičnost tak, jak existuje v aktuálně populárních jazycích, vede ke křehkému designu. Třída, která byla zděděna od jednoho předka, již nemůže být zděděna od ostatních. Nebezpečnou se stává i změna předka. Existují samozřejmě soukromé/chráněné modifikátory, ale také vyžadují značné psychické schopnosti, aby odhadly, jak se třída může změnit a jak ji může klientský kód používat. Dědičnost je tak nebezpečná a nepohodlná, že ji velké frameworky (jako Spring a EJB v Javě) opouštějí ve prospěch jiných, neobjektově orientovaných nástrojů (například metaprogramování). Důsledky jsou tak nepředvídatelné, že některé knihovny (jako je Guava) přiřazují svým třídám modifikátory, které zakazují dědění, a v novém jazyce Go bylo rozhodnuto o úplném opuštění hierarchie dědičnosti.

Polymorfismus

Možná je polymorfismus to nejlepší na objektově orientovaném programování. Díky polymorfismu vypadá při výstupu objekt typu Osoba jako „Shandorkin Adam Impolitovich“ a objekt typu Bod vypadá jako „“. Právě to vám umožňuje napsat „Mat1 * Mat2“ a získat součin matic, podobný součinu běžných čísel. Bez něj by nebylo možné číst data ze vstupního toku, aniž by bylo jedno, zda pocházejí ze sítě, souboru nebo řádku v paměti. Všude tam, kde jsou rozhraní, je také implikován polymorfismus.

Moc se mi líbí polymorfismus. Proto nebudu ani mluvit o jeho problémech v běžných jazycích. O zúženosti expedičního přístupu pouze podle typu a o tom, jak by se to dalo udělat, také pomlčím. Ve většině případů funguje jak má, což není špatné. Otázka zní: je polymorfismus právě tím principem, který odlišuje OOP od jiných paradigmat? Kdybyste se mě zeptali (a protože čtete tento text, můžete předpokládat, že jste se zeptali), odpověděl bych „ne“. A důvodem je stále stejné procento využití v kódu. Možná jsou rozhraní a polymorfní metody trochu častější než dědičnost. Porovnejte ale počet řádků kódu, který zabírají, s počtem řádků napsaných obvyklým procedurálním stylem – těch druhých je vždy více. Při pohledu na jazyky, které podporují tento styl programování, bych je nenazval polymorfními. Jazyky, které podporují polymorfismus – ano, to je normální. Ale ne polymorfní jazyky.

(To je však můj názor. Vždy můžete nesouhlasit.)

Takže abstrakce, zapouzdření, dědičnost a polymorfismus - to vše je v OOP, ale nic z toho není jeho integrálním atributem. Co je tedy OOP? Existuje názor, že podstata objektově orientovaného programování spočívá v objektech (zní to docela logicky) a třídách. Je to myšlenka kombinace kódu a dat a myšlenka, že objekty v programu odrážejí entity v reálném světě. K tomuto názoru se vrátíme později, ale nejprve si řekněme některá já.

Čí OOP je chladnější?

Z předchozí části je zřejmé, že programovací jazyky se mohou značně lišit ve způsobu implementace objektově orientovaného programování. Pokud vezmete souhrn všech implementací OOP ve všech jazycích, pak s největší pravděpodobností nenajdete jedinou vlastnost společnou všem. Abych tuto zoo nějak omezil a upřesnil úvahy, zaměřím se pouze na jednu skupinu – čistě objektově orientované jazyky, a to Java a C#. Termín „čistě objektově orientovaný“ v tomto případě znamená, že jazyk nepodporuje jiná paradigmata nebo je implementuje prostřednictvím stejného OOP. Například Python nebo Ruby nebudou čisté, protože můžete na nich snadno napsat plnohodnotný program bez jediné deklarace třídy.

Abychom lépe pochopili podstatu OOP v Javě a C#, podívejme se na příklady implementace tohoto paradigmatu v jiných jazycích.

Pokec. Na rozdíl od svých moderních protějšků byl tento jazyk dynamicky typován a k implementaci OOP používal styl předávání zpráv. Místo volání metod si objekty posílaly zprávy, a pokud příjemce nedokázal zpracovat to, co přišlo, jednoduše zprávu přeposlal někomu jinému.

Lisp obecný. Zpočátku se CL řídila stejným paradigmatem. Pak se vývojáři rozhodli, že psaní `(odeslat obj "nějaká-zpráva)` je příliš dlouhé, a převedli zápis na volání metody - `(nějaká metoda obj)` Dnes má Common Lisp vyspělý objektově orientovaný programovací systém ( CLOS) s podporou vícenásobné dědičnosti, multimetod a metatříd. Charakteristickým rysem je, že OOP v CL se netočí kolem objektů, ale kolem generických funkcí.

Clojure. Clojure má dva objektově orientované programovací systémy – jeden zděděný z Javy a druhý založený na multimetodách a více podobný CLOS.

R. Tento jazyk pro statistickou analýzu dat má také 2 objektově orientované programovací systémy - S3 a S4. Oba jsou zděděny z jazyka S (což není překvapivé, vzhledem k tomu, že R je open source implementace komerčního S). S4 do značné míry odpovídá implementacím OOP v moderních mainstreamových jazycích. S3 je lehčí varianta, jednoduše implementovaná pomocí samotného jazyka: je vytvořena jedna obecná funkce, která odesílá požadavky na základě atributu „class“ přijatého objektu.

JavaScript. Ideologicky podobný Smalltalku, i když používá jinou syntaxi. Místo dědičnosti používá prototypování: pokud požadovaná vlastnost nebo volaná metoda není v objektu samotném, pak je požadavek předán objektu prototypu (vlastnost prototypu všech objektů JavaScriptu). Zajímavostí je, že chování všech objektů třídy lze změnit nahrazením jedné z prototypových metod (velmi pěkně vypadá například přidání metody `.toBASE64` pro třídu string).

Krajta. Obecně se řídí stejným konceptem jako mainstreamové jazyky, ale také podporuje předávání vyhledávání atributů jinému objektu, jako v JavaScriptu nebo Smalltalku.

Haskell. V Haskellu není vůbec žádný stav, a tudíž žádné předměty v obvyklém smyslu. Stále však existuje určitý druh OOP: datové typy mohou patřit do jedné nebo více typových tříd. Například téměř všechny typy v Haskellu jsou ve třídě Eq (odpovědné za porovnávací operace mezi 2 objekty) a všechna čísla jsou navíc ve třídách Num (operace s čísly) a Ord (operace s čísly).<, <=, >=, >). V menstruačních jazycích typy odpovídají třídám (dat) a typové třídy odpovídají rozhraním.

Státní nebo bez státní příslušnosti?

Vraťme se ale k běžnějším objektově orientovaným programovacím systémům. Co jsem nikdy nedokázal pochopit, je vztah objektů k vnitřnímu stavu. Před studiem OOP bylo vše jednoduché a transparentní: existují struktury, které uchovávají několik souvisejících dat, existují procedury (funkce), které je zpracovávají. venčit (pes), vybrat (účet, částka). Pak předměty dorazily, a to bylo také v pořádku (ačkoli čtení programů bylo mnohem obtížnější - můj pes chodil [kdo?] a účet vybíral peníze [odkud?]). Pak jsem se dozvěděl o skrývání dat. Psa jsem ještě mohl venčit, ale už jsem se nemohl dívat na složení jeho potravy. Jídlo nic neudělalo (asi by se dalo napsat food.eat(pes), ale pořád dávám přednost tomu, aby můj pes jedl jídlo, než naopak). Jídlo jsou jen data a já (a můj pes) jsem k nim potřeboval mít přístup. Všechno Prostě. Ale už se nedalo zapadnout do rámce paradigmatu, jako do starých džínů z konce 90. let.

Dobře, máme metody přístupu k datům. Dopřejme si tento malý sebeklam a předstírejme, že naše data jsou skutečně skryta. Ale teď už vím, že objekty jsou především data a pak možná metody, které je zpracovávají. Pochopil jsem, jak psát programy, o co se při navrhování snažit.

Než jsem si stačil užít osvícení, uviděl jsem na internetu slovo bez státní příslušnosti (přísahal bych, že ho obklopovala zář a nad písmeny t a l viselo svatozář). Krátké studium literatury odhalilo úžasný svět transparentního toku řízení a jednoduchého multithreadingu bez nutnosti sledovat konzistenci objektů. Samozřejmě jsem se okamžitě chtěl dotknout tohoto nádherného světa. To však znamenalo naprosté odmítnutí jakýchkoli pravidel – nyní nebylo jasné, zda má pes chodit sám, nebo je k tomu potřeba speciální Walk Manager; potřebujete účet, nebo veškerou práci zařídí banka, a pokud ano, měla by odepisovat peníze staticky nebo dynamicky atd. Počet případů užití se exponenciálně zvýšil a všechny budoucí případy užití by mohly vést k potřebě velkého refaktoringu.

Stále nevím, kdy by měl být objekt bezstavový, kdy by měl být stavový a kdy by to měl být pouze datový kontejner. Někdy je to zřejmé, ale většinou ne.

Psaní: statické nebo dynamické?

Další věc, kterou se u jazyků jako C# a Java nemohu rozhodnout, je, zda jsou staticky nebo dynamicky typovány. Většina lidí pravděpodobně zvolá: „Jaký nesmysl! Samozřejmě staticky typováno! Typy jsou kontrolovány v době kompilace! Ale je to opravdu tak jednoduché? Je pravda, že zadáním typu X v parametrech metody si programátor může být jistý, že mu budou vždy předány objekty typu X? To je pravda - nemůže, protože... bude možné předat parametr typu X metodě X nebo jeho dědice. Zdálo by se, no a co? Potomci třídy X budou mít stále stejné metody jako X. Metody jsou metody, ale logika práce se může ukázat jako úplně jiná. Nejčastějším případem je situace, kdy se ukáže, že podřízená třída je optimalizována pro jiné potřeby než X a naše metoda může počítat přesně s touto optimalizací (pokud se vám takový scénář zdá nereálný, zkuste napsat plugin pro nějakou vyvinutou open source knihovnu - nebo strávíte několik týdnů analýzou architektury a algoritmů knihovny, nebo jednoduše náhodně zavoláte metody s vhodnou signaturou). Ve výsledku program funguje, ale rychlost provozu řádově klesne. I když z pohledu překladače je vše správně. Je příznačné, že Scala, která je nazývána nástupcem Javy, na mnoha místech standardně umožňuje předávat pouze argumenty zadaného typu, i když toto chování lze změnit.

Dalším problémem je hodnota null, kterou lze předat místo téměř jakéhokoli objektu v Javě a místo jakéhokoli objektu s možností Null v C#. null patří do všech typů najednou a zároveň nepatří k žádnému. null nemá ani pole ani metody, takže jakékoli jeho volání (kromě kontroly null) má za následek chybu. Zdá se, že na to jsou všichni zvyklí, ale pro srovnání je Haskell (a stejná Scala) nucen používat speciální typy (Možná v Haskellu, Option ve Scale) k zabalení funkcí, které by v jiných jazycích mohly vrátit hodnotu null. V důsledku toho o Haskell často říkají „je obtížné na něm zkompilovat program, ale pokud uspějete, pak s největší pravděpodobností funguje správně“.

Na druhou stranu mainstreamové jazyky zjevně nejsou dynamicky typovány, a proto nemají takové vlastnosti jako jednoduchá rozhraní a flexibilní postupy. V důsledku toho je také nemožné psát ve stylu Python nebo Lisp.

Jaký je rozdíl v tom, co se nazývá toto psaní, pokud jsou všechna pravidla stejně známá? Rozdíl je v tom, z jaké strany k návrhu architektury přistupujete. Existuje dlouhodobá debata o tom, jak postavit systém: vytvořit mnoho typů a málo funkcí, nebo málo typů a mnoho funkcí? První přístup je aktivně používán v Haskell, druhý v Lisp. Moderní objektově orientované jazyky používají něco mezi tím. Nechci říkat, že je to špatně - asi to má své výhody (ostatně bychom neměli zapomínat, že Java a C# jsou vícejazyčné platformy), ale pokaždé, když zakládám nový projekt, přemýšlím, kde začít navrhování - s typy nebo z funkčnosti.

A dál...

Nevím, jak modelovat problém. Předpokládá se, že OOP umožňuje zobrazit objekty reálného světa v programu. Nicméně ve skutečnosti mám psa (se dvěma ušima, čtyřmi tlapkami a obojkem) a bankovní účet (s manažerem, úředníky a přestávkou na oběd) a v programu - WalkManager, AccountFactory... no, dostanete idea. A nejde o to, že program má pomocné třídy, které neodrážejí objekty reálného světa. Faktem je, že kontrolovat změny toku. WalkingManager mě okrádá o radost z venčení mého psa a já dostávám peníze z bezduchého bankovního účtu (hej, kde je ta roztomilá holka, u které jsem minulý týden změnil peníze?).

Možná jsem snob, ale mnohem šťastnější jsem byl, když data v počítači byla jen data, i když popisovala mého psa nebo bankovní účet. Mohl jsem s daty dělat, co bylo vhodné, bez ohledu na skutečný svět.

Taky nevím, jak správně rozložit funkčnost. Pokud jsem v Pythonu nebo C++ potřeboval malou funkci pro převod řetězce na číslo, napsal jsem ji na konec souboru. V Javě nebo C# jsem nucen to dát do samostatné třídy StringUtils. V pre-OO jazycích bych mohl deklarovat ad hoc obal, který vrátí dvě hodnoty z funkce (vybranou částku a zůstatek na účtu). V OOP jazycích budu muset vytvořit plnohodnotnou třídu Transaction Result. A novému člověku na projektu (nebo i mně o týden později) bude tato třída připadat stejně důležitá a zásadní v architektuře systému. 150 souborů a všechny stejně důležité a základní – ach ano, transparentní architektura, úžasné úrovně abstrakce.

Nevím, jak psát efektivní programy. Efektivní programy využívají málo paměti – jinak bude garbage collector neustále zpomalovat provádění. Ale abyste provedli tu nejjednodušší operaci v objektově orientovaných jazycích, musíte vytvořit tucet objektů. Abych vytvořil jeden HTTP požadavek, musím vytvořit objekt typu URL, pak objekt typu HttpConnection, pak objekt typu Request... no, chápete. V procedurálním programování bych jednoduše zavolal několik procedur a předal jim strukturu vytvořenou na zásobníku. S největší pravděpodobností by se v paměti vytvořil pouze jeden objekt – pro uložení výsledku. V OOP musím neustále zahlcovat paměť.

Možná je OOP opravdu krásné a elegantní paradigma. Možná jen nejsem dost chytrý, abych to pochopil. Pravděpodobně existuje někdo, kdo dokáže vytvořit opravdu pěkný program v objektově orientovaném jazyce. No, můžu jim jen závidět.



Související články: