Objektovo orientované programovanie
Objektovo orientované programovanie (OOP) je spôsob programovania, pri ktorom sú časti programu zoskupované do väčších celkov – objektov, ktoré majú svoje funkcie v OOP zvané metódy, svoje vlastnosti v podobe premenných atp.. Objekty sú vytvárané na základe preddefinovaných kritérií – tried. Jednou z hlavných výhod OOP je prehľadnejší kód a princíp podobný reálnemu svetu.
V reálnom svete máme rovnako objekty, ktoré sú navzájom podobné, čiže zdieľajú svoje metódy a vlastnosti. Príklad si uveďme na smartfónoch. Triedou, z ktorej budeme vytvárať objekty, jednotlivé modely, bude trieda smartfón. Tá bude mať preddefinované metódy ako volanie, ktoré na každom smartfóne funguje rovnako, posielanie SMS, prehliadanie internetu atp.
Jedná sa totiž o funkcie, ktoré obsahuje každý smartfón. Trieda smartfón bude mať ďalej svoje vlastnosti ako názov, displej, procesor, …, ktoré bude nutné zadať pri vytváraní objektu a pre každý môžu byť rozdielne. Podľa triedy smartfón teraz môžeme vytvárať objekty – modely.
Prvý model smartfónu bude mať napríklad 5palcový Full HD displej a 4jadrový 1,2GHz procesor. Druhý model bude mať 5,5palcový displej s rozlíšením 2K a 8jadrový 1,8GHz procesor. Oba smartfóny majú rovnaké funkcie – metódy, dajme tomu, že majú rovnaký operačný systém. Rozdielne sú však ich parametre – vlastnosti, ako spomenutý procesor, displej.
My si názorný príklad neskôr ukážeme na dvojrozmernom geometrickom telese obdĺžnik, teda, budeme vytvárať objekty – obdĺžniky.
OOP v Pythone
Skôr ako si ukážeme názorný príklad, vysvetlíme si ešte pár vecí a znovu spomeniem, že v programovacom jazyku Python je objektom všetko. Každé číslo, funkcia, reťazec… Tieto objekty sa rovnako tvoria z tried a majú svoje atribúty – metódy a vlastnosti. Znovu si teda ukážme, ako zistíme typ premennej:
>>> type("Reťazec") <class 'str'>
Vidíme, že typom reťazca je trieda str (string), čiže objekt "Reťazec" bol vytvorený z triedy str. Táto trieda má svoje atribúty:
>>> dir(str) ['__add__', '__class__', …je tu toho veľa…, 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] >>> dir("Reťazec") ['__add__', '__class__', …je tu toho veľa…, 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] >>> dir("Reťazec") == dir(str) True
Vidíme, že objekt "Reťazec" vytvorený z triedy str má rovnaké atribúty ako trieda, z ktorej bol vytvorený. Keby sme vytvorili ďalší objekt, ktorý by bol rovnako reťazcom, jeho atribúty by boli identické. Môžete si všimnúť metódy, ktoré sme už v predchádzajúcich dieloch využívali, pri manipulácií s dátovými typmi.
Tieto triedy ako str, int, float, list, function, NoneType atď. sú vstavané v Pythone, a automaticky sa z nich tvoria jednotlivé objekty (funkcie, čísla, zoznamy...) často ukladané do premenných.
Jednoduchá trieda a objekt
Pre vytvorenie triedy v Pythone sa používa kľúčové slovíčko class
v kombinácii s názvom triedy, ktorý by podľa dohody mal začínať veľkým písmenom:
class Class: pass
Takto sme vytvorili triedu, ktorá je úplne prázdna a nerobí nič. Neznamená to však, že by sme nemohli vytvoriť objekt z takejto triedy. Ten vytvoríme zavolaním triedy podobne ako funkcie s tým, že jej obsah uložíme do premennej:
>>> class Class: ... pass ... >>> obj = Class() >>> type(obj) <class '__main__.Class'> >>> dir(obj) ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
Objekt obj je teraz objektom triedy Class, pričom tá je podtriedou hlavnej triedy programu, čiže podtriedou triedy __main__
. Môžete vidieť, že síce sme v triede Class žiadne atribúty nedefinovali, nejaké tam sú. Ide o základné atribúty každej triedy, ktorým sa ale takmer nebudeme venovať.
Trieda s metódou
Metóda je pomenovanie pre funkciu pri OOP. Metóda vytvorená v triede bude dostupná vo všetkých objektoch vytvorených z danej triedy. Tá samozrejme môže mať rôzne parametre. Pre zavolanie metódy vytvoreného objektu používame syntax objekt.metóda(argumenty)
.
>>> class Class: ... def method(x): ... print("Ja som metóda, ktorej bol zadaný argument:", x) ... >>> obj = Class >>> obj.method(42) Ja som metóda, ktorej bol zadaný argument: 42 >>> dir(obj) ['__class__', …, '__weakref__', 'method']
Trieda s premennou
V triede môžeme vytvárať aj premenné, ktoré budú rovnako ako metódy dostupné vo všetkých objektoch tejto triedy.
>>> class Class: ... name = "Meno" ... >>> obj = Class() >>> print(obj.name) Meno
self
Ak by sme v metóde objektu chceli pristupovať k jeho premenným, musíme mu istým spôsobom predať samého seba. To docielime použitím argumentu self
. Ten odkazuje na objekt, v ktorom sa metóda nachádza. Použitie: self.názov_premennej:
>>> class Class: ... name = "Meno" ... def what_is_your_name(self): ... print(self.name) ... >>> obj = Class() >>> obj.what_is_your_name () Meno
Použitie self
je dané konvenciou a všetky editory ho automaticky zvýrazňujú. Použiť sa však dá akákoľvek premenná, no určite sa to neodporúča.
Konštruktor – vytvárame unikátne objekty
Doteraz boli naše vytvorené objekty len klonmi nadradenej triedy, respektíve na danú triedu odkazovali (čítaj viac), pretože sa od triedy nijak nelíšili. Neoddeliteľnou súčasťou OOP je však vytváranie objektov, ktoré sú niečím špecifické, čo dosiahneme použitím konštruktoru. Trieda s konštuktorom vyzerá takto:
class Class: def __init__(self): pass
Ako to funguje? Konštruktor je špeciálna metóda __init__()
, ktorá sa automaticky volá pri vytváraní objektu. Môže mať svoje ďalšie parametre, použité napríklad na zapísanie nejakých vlastností objektu. Argumenty tejto metóde potom predáme pri vytváraní objektu:
object = Class(argumenty)
Argumenty sú automaticky predané konštruktoru.
Prejdime teda na názorný príklad, ktorý som spomínal úvode článku. Vytvoríme si triedu pre obdĺžnik tak, aby sme pomocou nej mohli vytvárať rôzne objekty, čiže rôzne obdĺžniky, ktoré budú mať svoje rozmery. Pre tento príklad nebudem používať interpreter, ale samostatný súbor, ktorý budeme spúšťať.
class Rectangle: def __init__(self, a, b): self.a = a self.b = b print("Obdĺžnik s rozmermi {}x{} bol vytvorený.".format(a, b)) def get_dimensions(self): """ Vráti tuple s rozmermi: (a, b) """ return self.a, self.b rect1 = Rectangle(1, 5) rect2 = Rectangle(20, 3) squar = Rectangle(42, 42) print("Rozmery rect1 sú {}x{}.".format(*rect1.get_dimensions())) print("Rozmery rect2 sú {}x{}.".format(*rect2.get_dimensions())) print("Rozmery squar sú {}x{}.".format(*squar.get_dimensions()))
Poznámka ku kódu: použitie hviezdičky pri *rect1.get_dimensions()
spôsobí, že je n-tica vrátená touto funkciou rozdelená na viacero argumentov, podľa počtu prvkov v nej.
Ukážka:.format(rect1.get_dimensions()) -> .format((1, 5))
.format(*rect1.get_dimensions()) -> .format(1, 5)
Toto použitie je možné pri všetkých iterovateľných dátových typoch.
Výstup:
$ python3 oop.py Obdĺžnik s rozmermi 1x5 bol vytvorený. Obdĺžnik s rozmermi 20x3 bol vytvorený. Obdĺžnik s rozmermi 42x42 bol vytvorený. Rozmery rect1 sú 1x5. Rozmery rect2 sú 20x3. Rozmery squar sú 42x42.
V príklade sme teraz z triedy Rectangle vytvárali objekty rect1, rect2 a squar s rôznymi rozmermi. Tie sa pri vytváraní objektu zapísali ako premenné a a b, a rovnako sa vypísala hláška, že obdĺžnik s rozmermi xy bol vytvorený. Následne sme pomocou klasickej funkcie print tieto rozmery vypísali, pričom rozmery sme od každého objektu získali pomocou jeho metódy get_dimensions
, ktorá pri zavolaní vrátila oba rozmery obalené v dátovom type tuple.
Skomplikovať si to teraz môžeme tým, že odlíšime špeciálny druh obdĺžnika – štvorec, pridáme vypočítanie obsahu a dĺžky uhlopriečky. Pre vypísanie týchto informácie potom použijeme cyklus for. Tak, tu to máme:
import math class Rectangle: def __init__(self, a, b=None): self.a = a self.b = b if b else a if self.a == self.b: self.shape = "Štvorec" else: self.shape = "Obdĺžnik" print("{} s rozmermi {}x{} bol vytvorený.".format( self.shape, self.a, self.b)) def get_dimensions(self): """ Vráti tuple s rozmermi: (a, b) """ return self.a, self.b def get_surface(self): """ Vypočíta a vráti veľkosť povrchu """ surface = self.a * self.b return surface def get_diagonal(self): """ Vypočíta a vráti dĺžku uhlopriečky """ diagonal = math.sqrt(self.a**2 + self.b**2) return diagonal rect1 = Rectangle(1, 5) rect2 = Rectangle(20, 3) squar = Rectangle(42) variables = {"rect1": rect1, "rect2": rect2, "squar": squar} text = """{shape} {name} Rozmery: {a}x{b} Obsah: {surface} Dĺžka uhlopriečky: {diagonal}""" for x in variables.keys(): obj = variables[x] print(text.format( shape=obj.shape, name=x, a=obj.get_dimensions()[0], b=obj.get_dimensions()[1], surface=obj.get_surface(), diagonal=obj.get_diagonal() ))
Výstup:
$ python3 oop.py Obdĺžnik s rozmermi 1x5 bol vytvorený. Obdĺžnik s rozmermi 20x3 bol vytvorený. Štvorec s rozmermi 42x42 bol vytvorený. Štvorec squar Rozmery: 42x42 Obsah: 1764 Dĺžka uhlopriečky: 59.39696961966999 Obdĺžnik rect1 Rozmery: 1x5 Obsah: 5 Dĺžka uhlopriečky: 5.0990195135927845 Obdĺžnik rect2 Rozmery: 20x3 Obsah: 60 Dĺžka uhlopriečky: 20.223748416156685
V kóde je ešte celkom veľa vecí, ktoré by sa dali vylepšiť, ako napríklad nulový rozmer jednej zo strán. O vylepšenie sa môžete pokúsiť sami.
Záver
Pre tento diel je to z objektového programovania v Python 3 všetko. V ďalšej časti budeme v OOP pokračovať a pozrieme sa na dedičnosť, privátne metódy a ešte niektoré špeciálne metódy pri objektoch.