🏗️ Objektově orientované programování

Formička a bábovky

O čem to je

Na pískovišti máš formičku na bábovku. Formička sama bábovka není, je to předpis, jak bábovka vypadá. Podle jedné formičky uděláš kolik bábovek chceš. Každá je samostatná: jednu rozšlápneš a ostatní zůstanou.

Objektově orientované programování (OOP) staví programy přesně takhle. Popíšeš předpis (třídu) a podle něj vyrábíš objekty. Hry, aplikace i webové stránky jsou plné objektů: hráčů, tlačítek, objednávek.

Nové pojmy

Třída (class)Předpis, který říká, jaká data budou objekty mít a co budou umět.Třída Pes: každý pes má jméno a umí štěkat.
Objekt (instance)Konkrétní věc vyrobená podle třídy. Má vlastní data.rex = Pes("Rex") a bety = Pes("Bety") jsou dva objekty třídy Pes.
AtributProměnná, která patří objektu. Popisuje jeho stav.rex.jmeno je „Rex“.
MetodaFunkce, která patří třídě. Popisuje, co objekt umí. Volá se přes tečku.rex.stekej()
Konstruktor __init__ a self__init__ je metoda, kterou Python zavolá při vytvoření objektu a nastaví v ní počáteční atributy. self je první parametr každé metody a znamená „tento objekt“.self.jmeno = jmeno uloží jméno do právě vytvářeného objektu.

Třída v Pythonu

  • class Ucet:
  •     def __init__(self, majitel):
  •         self.majitel = majitel
  •         self.zustatek = 0
  •  
  •     def vloz(self, castka):
  •         self.zustatek = self.zustatek + castka
  •  
  • u = Ucet("Eva")
  • u.vloz(100)
  • u.vloz(50)
  • print(u.zustatek)
Ucet("Eva") vytvoří nový objekt a zavolá __init__. Do self se dosadí nový objekt, do majitel „Eva“.
Objekt dostane atributy majitel = "Eva" a zustatek = 0.
u.vloz(100) zavolá metodu, self je u. Zůstatek je 100.
u.vloz(50) zvýší zůstatek na 150. print vypíše 150.
Vytvoříš a = Ucet("Eva") a b = Ucet("Petr"), pak a.vloz(10). Kolik je b.zustatek?
0. Každý objekt má vlastní atributy. Vklad na účet a se účtu b netýká, stejně jako rozšlápnutá bábovka neovlivní ostatní.
Přidej metodě vyber(castka) kontrolu, aby zůstatek nešel do mínusu.
def vyber(self, castka): → if castka > self.zustatek: print("Nedostatek peněz") → else: self.zustatek = self.zustatek - castka. Objekt sám hlídá, aby jeho data dávala smysl.
✗def vloz(castka): zustatek = zustatek + castka
✓def vloz(self, castka): self.zustatek = self.zustatek + castka

Metoda musí mít jako první parametr self a k atributům přistupovat přes self.. Bez toho by pracovala s lokální proměnnou, ne s daty objektu.

Třída je předpis, objekt konkrétní výrobek podle něj. Atributy jsou data objektu, metody jeho schopnosti. __init__ nastaví počáteční stav, self je odkaz na aktuální objekt.

Čtyři pilíře

O čem to je

Automobilka nevyrábí každé auto od nuly. Má obecný návrh „vozidlo“ a z něj odvozuje osobní auto, dodávku a autobus. Řidič navíc nepotřebuje rozumět motoru: má volant a pedály. OOP přenáší tyto principy do programování.

U maturity a pohovorů se často ptají na čtyři pilíře OOP. Projdeš je na jednom příkladu se zvířaty.

Nové pojmy

Třída a objektTřída je předpis (class), objekt je konkrétní instance vytvořená podle třídy. Objekt má atributy (data) a metody (funkce).class Zvire: a objekt Zvire("Micka").
ZapouzdřeníObjekt drží svá data a metody pohromadě a skrývá vnitřek. Okolí s ním pracuje jen přes metody. V Pythonu se „vnitřní“ atributy označují podtržítkem na začátku jména (_zustatek); je to dohoda, ne zámek.Zůstatek účtu měníš metodou vloz(), ne přímo.
DědičnostNová třída (potomek) převezme atributy a metody existující třídy (předka) a může je doplnit nebo změnit. Zápis class Pes(Zvire):.Pes je zvíře, proto může dědit ze třídy Zvire.
PolymorfismusRůzné objekty reagují na stejnou metodu po svém. Kód je volá stejně a nemusí řešit, o jaký objekt jde.zvuk() vrátí u psa „haf“, u kočky „mňau“.
AbstrakceZ reálné věci popíšeš jen to, co program potřebuje. Ostatní vynecháš.Ve školní evidenci má žák jméno a třídu, ne barvu očí.
Přepsání metody (override)Potomek definuje metodu se stejným jménem jako předek. Pro jeho objekty platí nová verze.Pes přepíše zvuk() předka.

Dědičnost a polymorfismus v kódu

  • class Zvire:
  •     def __init__(self, jmeno):
  •         self.jmeno = jmeno
  •     def zvuk(self):
  •         return "..."
  •     def predstav_se(self):
  •         return self.jmeno + ": " + self.zvuk()
  •  
  • class Pes(Zvire):
  •     def zvuk(self):
  •         return "haf"
  •  
  • class Kocka(Zvire):
  •     def zvuk(self):
  •         return "mňau"
  •  
  • for z in [Pes("Rex"), Kocka("Micka")]:
  •     print(z.predstav_se())

Vypíše „Rex: haf“ a „Micka: mňau“. Pes a Kocka nemají vlastní __init__ ani predstav_se, zdědily je. Přepsaly jen zvuk(). Cyklus volá stejnou metodu a každý objekt odpoví po svém: to je polymorfismus.

Potomek Student(Osoba) potřebuje navíc atribut trida. Jak napíše __init__?
def __init__(self, jmeno, trida): → super().__init__(jmeno) → self.trida = trida. super() zavolá konstruktor předka, který nastaví jméno. Pak se doplní, co je nové.
Má třída Auto dědit ze třídy Kolo?
Ne. Dědičnost vyjadřuje vztah „je“: pes je zvíře. Auto není kolo, auto kola má. Správně má auto atribut se seznamem kol (skládání objektů).

Zkus každý pilíř vysvětlit vlastními slovy a pak kartičku otoč.

  • Zapouzdření – data a metody pohromadě, vnitřek skrytý
  • Dědičnost – potomek převezme vlastnosti předka
  • Polymorfismus – stejná metoda, různé chování
  • Abstrakce – jen podstatné vlastnosti
✗Dědičnost použiju vždy, když chci v jedné třídě využít jinou (Auto dědí z Motor).
✓Dědičnost jen pro vztah „je“ (Pes je Zvire). Pro vztah „má“ dej objekt do atributu (self.motor = Motor()).

Auto není druh motoru. Špatná dědičnost dá autu metody motoru, které mu nepatří.

Čtyři pilíře OOP: zapouzdření (data skrytá za metodami), dědičnost (class Pes(Zvire)), polymorfismus (stejná metoda, různé chování) a abstrakce (jen podstatné). Dědit jen při vztahu „je“.