Objektově orientované programování (OOP) je dnes jedním z nejrozšířenějších přístupů při tvorbě softwaru. Ať už je tvořena desktopová aplikaci, backendové služby nebo mobilní aplikaci, tak OOP nabízí nástroje, jak organizovat složitost velkých systémů.
Cílem OOP je modelovat svět kolem nás pomocí objektů — entit, které kombinují stav (data) a chování (metody). Bylo vytvořeno s cílem zlepšit čitelnost kódu, zvýšit jeho znovu použitelnost a usnadnit údržbu.
Datový typ object
Datový typ object (resp. System.Object) je v C# základní typ, z kterého všechny typy dědí a jakýkoliv typ je možné na něj převést.
Tento typ definuje základní metody:
ToString() - Převádí objekt na řetězec.
Equals() - Porovnává dva objekty na rovnost. Ve výchozím stavu se jedná o referenční porovnání. Pokud se přepisuje, tak by se měl přepsat i GetHashCode().
GetHashCode() - Vrací číselný hash, který se používá např. v Dictionary pro identifikaci a porovnávání objektů.
GetType() - Vrací objekt typu Type, který reprezentuje informaci o konkrétním typu instance (v době běhu aplikace).
Boxing
Pojem, který označuje převod jednoduchého datového typu na referenční. Je realizován obalením jednoduchého datového typu typem object. Unboxing je opačná operace - tzn. rozbalení obaleného jednoduchého datového typu.
int i = 42;// Boxing: hodnota typu int se zabalí do objektu typu objectobject boxed = i;Console.WriteLine(boxed); // Výstup: 42Console.WriteLine(boxed.GetType()); // Výstup: System.Int32
Ukázka unboxing
object boxed = 42;// Unboxing: hodnota se vybalí zpět do typu intint j = (int)boxed;Console.WriteLine(j); // Výstup: 42
Pilíře OOP
OOP se skládá ze 3, resp. někde lze nalézt i 4 pilířů:
Zapouzdření (Encapsulation)
Dědičnost (Inheritance)
Polymorfismus (Polymorphism)
Abstrakce (Abstraction)
1. Zapouzdření (Encapsulation)
Zapouzdření slouží ke skrytí detailů implementace objektu před vnějším světem. Třída zveřejní pouze rozhraní (public členy - metody, vlastnosti, atd.), které dovoluje ostatním třídám s objektem pracovat, zatímco interní stav nebo implementace zůstávají chráněné. Ostatní vývojáři by pro využívání třídy neměli potřebovat vědět, jak třída funguje uvnitř.
Význam
Ochrana dat před nechtěnou manipulací
Umožňuje měnit implementaci bez dopadu na okolní kód
public class BankAccount{ private decimal balance; public void Deposit(decimal amount) { if (amount <= 0) throw new ArgumentException("Amount must be positive"); balance += amount; } public void Withdraw(decimal amount) { if (amount > balance) throw new InvalidOperationException("Insufficient funds"); balance -= amount; } public decimal GetBalance() { return balance; }}
2. Dědičnost (Inheritance)
Dědičnost umožňuje vytvořit novou třídu (potomka) na základě existující třídy (rodiče). Smyslem potomků je rozšířit funkce rodiče.
Potomek dědí členy rodiče a může:
přidávat nové vlastnosti/metody
měnit (přepisovat) chování zděděných metod
Syntaxe dědičnosti
public class Potomek : Rodic{}
Význam
Opakované využití kódu
Sdílení společného chování mezi více třídami
Umožňuje hierarchickou organizaci tříd
Typy
Jednoduchá dědičnost - jedna třída může mít nejvýše jednoho rodiče
Pokud není rodič explicitně nastaven, využívá se implicitní rodič - object
Reprezentanti: C#, Java…
Násobná dědičnost - jedna třída může mít více rodičů
Větší volnost v nastavování rodičovství tříd
Přináší problémy jak správně sestavit hierarchii tříd
Reprezentanti: C++, Python
Ukázka
public class Animal{ private int _age; public Animal(int age) { _age = age; } public string Name { get; set; } public void Eat() { Console.WriteLine($"{Name} is eating."); }}public class Dog : Animal{ public Dog(int age) : base(age) // předání age konstruktoru rodiče { } public void Bark() { Console.WriteLine($"{Name} says Woof!"); }}
Ukázka použití:
var dog = new Dog(7);dog.Name = "Buddy";dog.Eat(); // zděděno z Animaldog.Bark(); // definováno v Dog
Klíčové slovo base
Pro volání konstuktoru rodiče nebo explicitní specifikaci, že se má volat metoda v rodiči lze využít klíčové slovo base.
base.ParentMethodName();
3. Polymorfismus (Polymorphism)
Polymorfismus znamená česky mnohotvárnost a umožňuje použít různé objekty stejným způsobem. Jinými slovy: různé třídy implementují stejné rozhraní nebo přepisují metody rodiče, ale každá se chová jinak.
Například v e-shopu mohou různé platební metody (karta, převod, kryptoměna) sdílet stejnou metodu Pay(), ale každá ji provede jinak. Program přitom neřeší, jaká konkrétní metoda se používá – pracuje s nimi jednotně.
Obsahuje dva základní přístupy:
Přetěžování metod
Přepisování metod
Info
Takovéto dělení polymorfismu je velké zjednodušení pro účely těchto stránek. Pro správné dělení by bylo nutné zavést mnoho nových pojmů a jednalo by se o zbytečnou komplikaci velmi nad rámec těchto stránek.
3.1 Přetěžování metod (Method overloading)
Umožňuje vytvořit více metod se stejným názvem a různým počtem nebo datovými typy parametrů. Návratový datový typ musí být vždy stejný. Jednotlivé metody musí být od sebe kompilátorem odlišitelné.
public Color GetColor(int r, int g, int b){ //...}public Color GetColor(string hex){ // ...}
3.2 Přepisování metod (Method overriding)
Potomek nahradí implementaci metody v rodiči vlastní implementací. Přepisovat je možné pouze metody, které jsou:
virtuální
Klíčové slovo virtual
Obsahuje výchozí implementaci
Přepsání takovéto metody není povinné. Pokud není přepsána, tak se využije výchozí implementace.
Např. metoda ToString() na datovém typu object
abstraktní
Klíčové slovo abstract
Neobsahuje výchozí implementaci
Nutné ji přepsat vždy ve třídě, která není abstraktní a dědí z abstraktní třídy
Ukázka výchozí implementace metody ToString
public virtual string ToString(){ return this.GetType().FullName;}
Význam
Umožňuje tvorbu obecného kódu pracujícího s různými typy
Zvyšuje flexibilitu architektury
Podporuje princip open/closed – systém je otevřený pro rozšíření, ale uzavřený pro změny
Ukázka
public class Animal{ public virtual void Speak() { Console.WriteLine("Some generic animal sound."); }}public class Dog : Animal{ public override void Speak() { Console.WriteLine("Woof!"); }}public class Cat : Animal{ public override void Speak() { Console.WriteLine("Meow!"); }}public class Parrot : Animal { }
Ukázka použití:
Animal myAnimal;myAnimal = new Dog();myAnimal.Speak(); // Woof!myAnimal = new Cat();myAnimal.Speak(); // Meow!myAnimal = new Parrot();myAnimal.Speak(); // Some generic animal sound.
Chybějící použití polymorfismu
Při každém přidání dalšího potomka třídy Animal je nutné projít všechen kód a přidat do if/switch potřebné větve kódu s tím, co se má stát, když se metoda zavolá pro nový typ
using System;public class Animal{ public string Name { get; set; }}public class Dog : Animal { }public class Cat : Animal { }public class AnimalSpeaker{ public void MakeAnimalSpeak(Animal animal) { if (animal is Dog) { Console.WriteLine("Woof!"); } else if (animal is Cat) { Console.WriteLine("Meow!"); } else { Console.WriteLine("Some generic animal sound."); } }}
Abstrakce
Abstrakce umožňuje skrýt složitost a nabídnout pouze nezbytné rozhraní pro použití funkcionality. V OOP se to často realizuje pomocí:
abstraktních tříd (abstract class)
rozhraní (interface)
Význam
Definuje smlouvu (anglicky: contract), kterou musí třídy implementovat
Usnadňuje testování a výměnu komponent
Odděluje „co děláme” od „jak to děláme”
Abstraktní třídy
Abstraktní třídy jsou třídy, které jsou označeny pomocí klíčového slova abstract. Společného mají to, že podporují všechny funkce tříd - i např. konstruktory. Na rozdíl od běžných tříd se ale jedná o:
nedokončený typ, který nelze přímo instanciovat
mohou obsahovat:
abstraktní členy (bez implementace) → potomek je musí implementovat
implementované členy (tj. metody s kódem)
umožňuje sdílet společné chování (logiku) mezi různými potomky
Abstraktní metody
Abstraktní metody jsou metody, u kterých není definována implementace v rodičovi, ale potomci si ji musí dodefinovat.
Kdy musí být třída abstraktní?
Pokud má třída alespoň jeden abstraktní člen (metodu, vlastnost, atd.), tak musí být také abstraktní.
Ukázka
public abstract class Shape{ public string Color { get; set; } // abstraktní metoda - potomek MUSÍ implementovat public abstract double GetArea(); // běžná metoda - potomek může použít rovnou public void PrintColor() { Console.WriteLine($"Shape color is {Color}"); }}public class Circle : Shape{ public double Radius { get; set; } public override double GetArea() { return Math.PI * Radius * Radius; }}
Ukázka použití:
Shape shape = new Circle{ Radius = 5, Color = "Red"};shape.PrintColor(); // Shape color is RedConsole.WriteLine(shape.GetArea()); // 78.5398...
Rozhraní
Rozhraní se označuje pomocí klíčového slova interface a:
definuje pouze smlouvu (signatury metod, vlastností, eventů)
neobsahuje žádnou implementaci (výjimkou jsou default metody od C# 8.0)
třída nebo struktura může implementovatvíce rozhraní najednou
Dědit vs. implementovat
Třídy se dědí, ale rozhraní se implementují!
Ukázka
public interface ILogger{ void Log(string message);}public class ConsoleLogger : ILogger{ public void Log(string message) { Console.WriteLine($"[LOG] {message}"); }}
Ukázka použití:
ILogger logger = new ConsoleLogger();logger.Log("Application started.");
Srovnání abstraktních tříd a rozhraní
Vlastnost
Abstraktní třída
Rozhraní
Instanciovatelná?
Ne
Ne
Může obsahovat implementaci?
Ano
Od C# 8.0 (default)
Může obsahovat uložení stav (např. pole, proměnná…)?