Pozn. ke kódu: obecné principy jsou popsané slovně, ke každému klíčovému pojmu je pak ukázka reálného kódu v Pythonu a v C# vedle sebe – oba jazyky se ve výuce běžně používají a princip OOP je v nich (i ve všech dalších běžných OOP jazycích) stejný.
Třída a objekt
- objektově orientované programování (OOP) = způsob návrhu programu, kdy je problém rozdělen na spolupracující objekty odpovídající reálným entitám (uživatel, objednávka, vozidlo…)
- třída (class) = šablona/předpis, který popisuje, jaké atributy (vlastnosti, data) a jaké metody (chování, operace) budou mít objekty tohoto typu
- objekt (instance) = konkrétní realizace třídy vytvořená v paměti; ze stejné třídy lze vytvořit libovolný počet objektů, každý má vlastní hodnoty atributů
- analogie: třída je jako recept, objekt je konkrétní upečený koláč podle receptu – receptů je jeden, koláčů z něj může vzniknout mnoho a každý může být trochu jiný (jiná velikost, náplň)
- atribut = proměnná patřící objektu, popisuje jeho stav (např. objekt Auto má atributy barva, znacka, rychlost)
- metoda = funkce patřící třídě, popisuje chování objektu a má přístup k jeho atributům (např. metoda zrychli() mění atribut rychlost)
Python:
class Auto:
def zrychli(self, o_kolik):
self.rychlost += o_kolik
# vytvoření objektu (instance) třídy Auto
auto1 = Auto()
auto1.barva = "červená" # atribut
auto1.rychlost = 0
auto1.zrychli(20) # volání metody
print(auto1.rychlost) # 20
C#:
class Auto
{
public string Barva; // atribut
public int Rychlost;
public void Zrychli(int oKolik) // metoda
{
Rychlost += oKolik;
}
}
// vytvoření objektu (instance) třídy Auto
Auto auto1 = new Auto();
auto1.Barva = "červená";
auto1.Rychlost = 0;
auto1.Zrychli(20);
Console.WriteLine(auto1.Rychlost); // 20
Konstruktor
- konstruktor = speciální metoda volaná automaticky při vytváření nového objektu (operátorem
newnebo ekvivalentem); slouží k inicializaci atributů na počáteční hodnoty - konstruktor má obvykle stejný název jako třída a nevrací žádnou hodnotu
Python: konstruktor se jmenuje vždy __init__ a jeho prvním parametrem je self (odkaz na vytvářený objekt):
class Auto:
def __init__(self, zadana_barva): # konstruktor
self.barva = zadana_barva
self.rychlost = 0
def zrychli(self, o_kolik):
self.rychlost += o_kolik
auto1 = Auto("červená") # zavolá se __init__
auto1.zrychli(20)
print(auto1.barva, auto1.rychlost) # červená 20
C#: konstruktor má stejný název jako třída a nemá návratový typ:
class Auto
{
public string Barva;
public int Rychlost;
public Auto(string zadanaBarva) // konstruktor
{
Barva = zadanaBarva;
Rychlost = 0;
}
public void Zrychli(int oKolik)
{
Rychlost += oKolik;
}
}
Auto auto1 = new Auto("červená"); // zavolá se konstruktor
auto1.Zrychli(20);
Console.WriteLine($"{auto1.Barva} {auto1.Rychlost}"); // červená 20
Přetěžování metod a konstruktoru
- přetěžování (overloading) = v rámci jedné třídy může existovat víc metod (nebo konstruktorů) se stejným názvem, které se liší počtem nebo typem parametrů
- při volání se podle zadaných argumentů automaticky vybere odpovídající verze
- typický příklad: bezparametrický konstruktor (nastaví výchozí hodnoty) vedle konstruktoru, který přijímá vstupní hodnoty
Python nemá klasické přetěžování (novější definice metody/konstruktoru se stejným jménem by tu předchozí jen přepsala) – řeší se výchozími hodnotami parametrů:
class Auto:
def __init__(self, barva="bílá"): # výchozí hodnota nahrazuje přetížení
self.barva = barva
auto1 = Auto() # barva = "bílá"
auto2 = Auto("červená") # barva = "červená"
C# skutečné přetěžování podporuje – v jedné třídě může být víc konstruktorů/metod se stejným jménem:
class Auto
{
public string Barva;
public Auto() // konstruktor bez parametrů
{
Barva = "bílá";
}
public Auto(string zadanaBarva) // přetížený konstruktor s parametrem
{
Barva = zadanaBarva;
}
}
Auto auto1 = new Auto(); // Barva = "bílá"
Auto auto2 = new Auto("červená"); // Barva = "červená"
Zapouzdření
- zapouzdření (encapsulation) = skrytí vnitřního stavu (atributů) objektu před vnějším přístupem; k datům se přistupuje pouze přes veřejné metody objektu, ne přímo
- atributy bývají označené jako private (přístupné jen uvnitř třídy), metody, které je zpřístupňují navenek, jako public
- typicky se používají gettery (metody pro čtení hodnoty atributu) a settery (metody pro nastavení hodnoty, mohou zahrnovat kontrolu platnosti – validaci)
- důvod: ochrana integrity dat (nelze nastavit neplatnou hodnotu zvenčí), oddělení toho, jak je třída použitá (rozhraní), od toho, jak je uvnitř implementovaná – vnitřní implementaci lze později změnit, aniž by se muselo měnit cokoli, kde je třída používaná
Python:
class Auto:
def __init__(self):
self._rychlost = 0 # podtržítko = konvence pro "privátní" atribut
def get_rychlost(self): # getter
return self._rychlost
def set_rychlost(self, hodnota): # setter s validací
if hodnota < 0:
raise ValueError("Rychlost nemůže být záporná")
self._rychlost = hodnota
auto1 = Auto()
auto1.set_rychlost(50)
print(auto1.get_rychlost()) # 50
C#:
class Auto
{
private int rychlost; // private = přístupné jen uvnitř třídy
public int Rychlost // vlastnost (property) = getter + setter dohromady
{
get { return rychlost; }
set
{
if (value < 0)
throw new ArgumentException("Rychlost nemůže být záporná");
rychlost = value;
}
}
}
Auto auto1 = new Auto();
auto1.Rychlost = 50;
Console.WriteLine(auto1.Rychlost); // 50
Dědičnost
- dědičnost (inheritance) = mechanismus, kdy jedna třída (potomek, odvozená třída) přebírá atributy a metody jiné třídy (předek, rodičovská/základní třída) a může je doplnit nebo upravit
- šetří opakování kódu – společné vlastnosti se definují jednou v předkovi
- příklad: obecná třída
Vozidlo(atributy značka, rychlost; metoda zrychli()) a od ní odvozené třídyAutoaMotorka, které dědí vše zVozidloa přidávají si vlastní atributy/metody navíc - přepsání metody (override) = potomek může definovat vlastní verzi zděděné metody, která nahradí/upraví chování rodičovské metody
Python:
class Vozidlo:
def __init__(self, znacka):
self.znacka = znacka
self.rychlost = 0
def zrychli(self, o_kolik):
self.rychlost += o_kolik
class Auto(Vozidlo): # Auto dědí od Vozidlo
def __init__(self, znacka, pocet_dveri):
super().__init__(znacka) # volání konstruktoru předka
self.pocet_dveri = pocet_dveri
auto1 = Auto("Škoda", 5)
auto1.zrychli(30) # zděděná metoda
print(auto1.znacka, auto1.rychlost, auto1.pocet_dveri)
C#:
class Vozidlo
{
public string Znacka;
public int Rychlost;
public Vozidlo(string znacka)
{
Znacka = znacka;
}
public void Zrychli(int oKolik)
{
Rychlost += oKolik;
}
}
class Auto : Vozidlo // Auto dědí od Vozidlo
{
public int PocetDveri;
public Auto(string znacka, int pocetDveri) : base(znacka) // volání konstruktoru předka
{
PocetDveri = pocetDveri;
}
}
Auto auto1 = new Auto("Škoda", 5);
auto1.Zrychli(30); // zděděná metoda
Console.WriteLine($"{auto1.Znacka} {auto1.Rychlost} {auto1.PocetDveri}");
Polymorfismus
- polymorfismus = schopnost objektů různých tříd (typicky ve stejné hierarchii dědičnosti) reagovat na stejné volání metody odlišným, pro danou třídu specifickým způsobem
- příklad: třídy
PesaKočkaobě dědí zeZvířea obě mají metoduvydejZvuk(), ale každá ji implementuje jinak (Pes→ “haf”,Kočka→ “mňau”); kód, který metodu volá, nemusí vědět, o jaký konkrétní typ zvířete jde - statický polymorfismus = řešen přetěžováním (viz výše), rozhoduje se už při překladu podle typů parametrů
- dynamický polymorfismus = řešen přepisováním zděděných metod, o tom, která konkrétní verze metody se zavolá, se rozhoduje až za běhu programu podle skutečného typu objektu (tzv. pozdní vazba)
Python:
class Zvire:
def vydej_zvuk(self):
return "..."
class Pes(Zvire):
def vydej_zvuk(self): # přepsání (override) metody předka
return "Haf!"
class Kocka(Zvire):
def vydej_zvuk(self):
return "Mňau!"
zvirata = [Pes(), Kocka()]
for zvire in zvirata:
print(zvire.vydej_zvuk()) # Haf! / Mňau! - podle skutečného typu objektu
C#:
class Zvire
{
public virtual string VydejZvuk() // virtual = potomci ji mohou přepsat
{
return "...";
}
}
class Pes : Zvire
{
public override string VydejZvuk() // přepsání (override) metody předka
{
return "Haf!";
}
}
class Kocka : Zvire
{
public override string VydejZvuk()
{
return "Mňau!";
}
}
List<Zvire> zvirata = new List<Zvire> { new Pes(), new Kocka() };
foreach (Zvire zvire in zvirata)
{
Console.WriteLine(zvire.VydejZvuk()); // Haf! / Mňau! - podle skutečného typu objektu
}
Kolekce objektů
- kolekce = struktura, která uchovává víc objektů pohromadě (pole objektů, seznam/list, množina apod.)
- kolekce obvykle obsahuje instance stejné třídy (nebo jejích potomků), lze přes ni procházet cyklem a volat metody na jednotlivých objektech
Python:
seznam_aut = []
seznam_aut.append(Auto("červená"))
seznam_aut.append(Auto("modrá"))
for auto in seznam_aut:
auto.zrychli(10)
C#:
List<Auto> seznamAut = new List<Auto>();
seznamAut.Add(new Auto("červená"));
seznamAut.Add(new Auto("modrá"));
foreach (Auto auto in seznamAut)
{
auto.Zrychli(10);
}
Návrh programu s využitím OOP
- postup návrhu:
- identifikace reálných entit v problému a jejich převod na třídy (podstatná jména v zadání bývají kandidáti na třídy)
- určení atributů (co entita „má“) a metod (co entita „umí“/co se s ní dělá)
- určení vztahů mezi třídami – dědičnost (je typem), asociace/použití (má/používá)
- implementace tříd, vytvoření objektů a jejich vzájemná spolupráce v programu
- dobře navržené třídy by měly mít jasně danou odpovědnost a co nejvíc využívat zapouzdření, aby se v kódu dalo snadno orientovat a upravovat ho
Verzovací systém
- verzovací systém (version control system, např. Git) = nástroj pro sledování historie změn v kódu a spolupráci více lidí na stejném projektu
- repozitář (repository) = úložiště projektu se všemi uloženými verzemi; může být lokální (na počítači) a vzdálený/remote (např. na GitHubu/GitLabu)
- commit = uložení aktuálního stavu (snímku) změn do historie repozitáře, doplněné popisem, co se změnilo a proč
- větev (branch) = oddělená linie vývoje, umožňuje pracovat na nové funkci nebo opravě, aniž by se ovlivnila hlavní (stabilní) verze kódu; po dokončení se větev sloučí (merge) zpět
- push / pull = odeslání lokálních commitů na vzdálený repozitář / stažení změn ze vzdáleného repozitáře
- význam: zálohování historie, možnost vrátit se k libovolné dřívější verzi, bezpečná spolupráce víc lidí na stejném kódu bez přepisování práce druhých