Objektově orientované programování
Objektově orientované programování je speciální přístup k programování, který jako základní stavební jednotky používá tzv. objekty. Objekt je ucelený soubor proměnných a funkcí pro práci s nimi.
Existuje široká škála různých programovacích jazyků, které tento způsob programování podporují – například Smalltalk, Eiffel, Ruby, C++, C#, Java, Python, Perl nebo PHP.
Objektově orientované programování je na rozdíl od procedurálního programování přehlednější, jednodušší, bezpečnější a umožňuje pracovat v různých úrovních abstrakce. Také dramaticky zvyšuje znovupoužitelnost kódu díky modularitě. Každý objekt může být považován za samostatný modul, který lze použít bez znalosti jeho vnitřní struktury.
Historie
Historie objektově orientovaného programování začala v šedesátých letech 20. století. Prvním programovacím jazykem s třídami a objekty byla Simula 67, za jejímž vznikem stáli dva norští vědci jménem Kristen Nygaard a Ole-Johan Dahl. Tento jazyk byl původně určen k simulaci interakcí několika lodí.
Simula 67 inspirovala několik vědců z firmy XEROX, kteří vytvořili vlastní jazyk Smalltalk a začali poprvé používat termín „objektově orientované programování“. Simula 67 silně ovlivnila i Bjarne Stroustrupa při návrhu jazyka C++.
Cíle
Robustnost
Robustní software by se měl chovat korektně za všech okolností, tedy dávat správné výstupy pro správné vstupy a rozumně reagovat na ty nesprávné. Měl by také pokud možno být připraven i na neočekávané události – nekorektní ukončení, ztráta spojení, nenalezený soubor, nedostatek paměti, apod. Softwarové chyby mohou mít i smrtelné následky. Například v letech 1985 – 1987 ozářil lékařský přístroj Therac-25 nesprávně šest pacientů, ze kterých tři zemřeli na přímé následky. Všech šest selhání bylo způsobeno nedostatečnou robustností programu.
Přizpůsobitelnost
Některé programy jsou používané nepřetržitě i několik let. Jsou to například operační systémy, prohlížeče, ovladače a firmware. Proto musí být možné tento software postupně upravovat a vylepšovat s co nejmenším úsilím a rizikem. Přizpůsobitelný software je také možné jednodušeji přenášet na jiné platformy.
Znovupoužitelnost
Stejný kód použitý v různých částech systému snižuje jeho složitost a tak i náklady na jeho vývoj. Stejně tak je možné kód přenášet i mezi projekty. Často přenášený kód bývá již odladěný a dobře otestovaný. Touto cestou jdou různé frameworky, které si kladou za cíl usnadnit vývoj a přispět k větší disciplinovanosti vývojářů. Projekty na nich postavené ale na druhou stranu přebírají i všechny jejich nedostatky a závislost na třetí straně.
Základní pojmy
Objekt (object)
Objekt je základní a nedělitelnou jednotkou objektově orientovaného programování. Každý objekt může představovat skutečný objekt z reálného světa, ale není to podmínkou. Lze však obecně říci, že se jedná o ucelený soubor proměnných a funkcí pro práci s nimi.
Třída (class)
Třída je schéma popisující vnitřní strukturu objektu. Každá třída je zobecněním množiny objektů se shodnou vnitřní strukturou a chováním. Třídy nabývají různé úrovně abstrakce. Reálnou analogií k obecné třídě by byla například Rostlina, zatímco o něco konkrétnější třídou by byla Květina.
Instance (instance)
Instance třídy je datová struktura v paměti počítače, vytvořená na základě deklarace dané třídy. Jednotlivé instance se od sebe liší umístěním (adresou) v paměti. Adresu právě prováděné instance mívají objektově orientované programovací jazyky k dispozici pod speciálním klíčovým slovem (např. this).
Atribut (attribute)
Atribut je označení pro vnitřní proměnnou objektu. Každá instance třídy má v paměti prostor pro vlastní hodnoty všech svých atributů. Existují také speciální statické proměnné (třídní proměnné), které nejsou uloženy v rámci instance, ale v rámci třídy. Důsledkem je úplná nezávislost na instancích. Hodnota každé statické proměnné je tedy k dispozici všem proměnným, ale její změny provedené jednou instancí se ihned projeví i v těch ostatních.
Příklad statické proměnné (Java)
{
private static int counter = 0;
public Counter ()
{
Counter.counter ++;
System.out.println ("byla vytvořena " + Counter.counter + ". instance");
}
}
zdrojový kód (Java) - zobrazit (188 znaků)
{
Counter e1 = new Counter ();
// vypíše "byla vytvořena 1. instance"
Counter e2 = new Counter ();
// vypíše "byla vytvořena 2. instance"
Counter e3 = new Counter ();
// vypíše "byla vytvořena 3. instance"
}
zdrojový kód (Java) - zobrazit (259 znaků)
Metoda (method)
Metoda je označení pro vnitřní funkci objektu. Podobně jako statické proměnné, existují také statické metody (třídní metody), které jsou úplně nezávislé na instancích. Nemají tedy přístup k adrese právě prováděné instance, a tak nemohou pracovat s nestatickými atributy a metodami dané instance. Lze je volat z metody jakéhokoli typu (obyčejné i statické).
Příklad statické metody (Java)
{
private int x;
private int y;
public Point (int x, int y)
{
this.x = x;
this.y = y;
}
public static double getDistance (Point p1, Point p2)
{
return Math.hypot (p1.x - p2.x, p1.y - p2.y);
}
}
zdrojový kód (Java) - zobrazit (242 znaků)
{
Point p1 = new Point (0, 0);
Point p2 = new Point (3, 4);
// statická metoda se nevolá přes instanci, ale přes třídu
double d = Point.getDistance (p1, p2);
// vypíše 5.0
System.out.println (d);
}
zdrojový kód (Java) - zobrazit (251 znaků)
Konstruktor (constructor)
Konstruktor je speciální metoda, která se automaticky zavolá při vytváření objektu. Slouží k inicializaci (počátečnímu nastavení) jeho atributů a nic jiného by dělat neměla. Každá třída musí mít konstruktor definovaný. Není-li vytvořen programátorem, bývá automaticky vygenerován při kompilaci.
Příklad (Java)
zdrojový kód (Java) - zobrazit (75 znaků)
Rozhraní (interface)
Rozhraní je množina hlaviček metod, určující jejich název, návratový typ a parametry. Obsahuje-li třída všechny metody, uvedené v rozhraní, říká se, že toto rozhraní implementuje. Dvě třídy, které implementují stejné rozhraní, jsou vzhledem k tomuto rozhraní volně zaměnitelné.
Zapouzdření (encapsulation)
Princip zapouzdření říká, že by žádný objekt neměl mít jakoukoliv znalost o vnitřní struktuře ostatních objektů, ani možnost do nich vstupovat a pracovat s jeho vnitřními metodami a proměnnými. Jediná možnost, jak s objektem pracovat, by měla být volání jeho veřejných metod.
Kvůli principu zapouzdření vznikly tzv. modifikátor přístupu, které u každého atributu a metody označují, zda je vnějšímu světu viditelná a dostupná (public) nebo vnitřní a nedostupná (private, protected). K proměnným a metodám označeným jako protected mají přístup pouze potomci dané třídy.
Dědičnost
Dědičnost umožňuje vytvářet hiearchii tříd a zpravidla slouží ke snižování obecnosti a zvyšování specializace. Jedna třída vystupuje v roli rodiče a druhá v roli potomka. Potomek dědí od rodiče všechny jeho dostupné metody (protected, public) a podle libosti může změnit jejich implementaci či přidat metody nové. V některých programovacích jazycích je možná dědičnost vícenásobná (od několika tříd najednou).
Dědičnost umožňuje programátorovi:
- bez zbytečné práce a duplicity kódu zachovat to, co bylo v předkovi dobré
- dodat to, co předkovi chybí
- změnit to, co mu v předkovi nevyhovuje
Příklad (Java)
Nejprve vytvoříme třídu Car (auto). U auta budeme sledovat jeho značku a typ (type) a rychlost (velocity). Dále se u aut chceme dozvědět, zda uvezou náklad o dané hmotnosti (metoda canCarryCargo()). Obyčejné auto obecné neuveze nic, a tak vždy vrátíme false.
{
private String type;
private double velocity;
private int passengers;
public Car (String type)
{
this.type = type;
this.velocity = 0;
this.passengers = 0;
}
public boolean canCarryCargo (int weight)
{
return false;
}
}
zdrojový kód (Java) - zobrazit (273 znaků)
Nyní využijeme dědičnosti a na základě třídy Car vytvoříme potomka Lorry (dodávka). Tak vytvoříme speciální druh automobilu, který uveze jakýkoliv náklad až do maximální povolené hmotnosti (maxWeight).
{
private int maxWeight;
public Lorry (String type, int maxWeight)
{
super (type);
this.maxWeight = maxWeight;
}
@Override
public boolean canCarryCargo (int weight)
{
return (weight <= this.maxWeight);
}
}
zdrojový kód (Java) - zobrazit (266 znaků)
Polymorfizmus (polymorphism)
Polymorfizmus úzce souvisí s dědičností. Je to schopnost potomka vystupovat v roli libovolného rodiče. To je možné, protože potomek vždy obsahuje ty samé veřejné metody jako jeho předchůdce, metody může pouze přidávat nebo měnit jejich konkrétní implementaci.
Příklad (Java)
Nejprve nadefinujeme nejobecnější třídu Person (osoba). Každá osoba bude mít jméno (name) a příjmení (surname). Mohli bychom ještě přidat například datum narození, trvalé bydliště, zemi, zájmy… zkrátka vše, co potřebujeme a co lze pozorovat u každé osoby. Není však vhodné přidávat proměnné jako je plat (salary), protože ne každá osoba pracuje – například děti nebo křováci.
zdrojový kód (Java) - zobrazit (76 znaků)
Pro vytvoření seznamu zaměstnanců nás zajímá pracovní pozice (job), plat (salary) a oddělení (department). Každý zaměstnanec by měl tyto údaje mít. Zároveň je každý zaměstnanec nepochybně osobou – alespoň do doby, než začneme zaměstnávat roboty nebo mimozemšťany.
{
public String job;
public String department;
public int salary;
//...
}
zdrojový kód (Java) - zobrazit (116 znaků)
Speciálním druhem zaměstnance je vedoucí či manažer (manager). Ten se od obyčejného zaměstnance vyznačuje tím, že má pod sebou nějaké podřízené. Zároveň je však každý vedoucí i obyčejným zaměstnancem.
{
public List<Employee> subordinates;
//...
}
zdrojový kód (Java) - zobrazit (85 znaků)
Nyní vytvoříme seznam všech pracovníků a ukážeme si práci s polymorfizmem. Ten umožní se zaměstnanci (Employee) pracovat jako s osobami (Person) a s vedoucími (Manager) jako zaměstnanci i osobami. Všimněte si, že třída A, která dědí od třídy B, od ní zároveň přebírá i veškerou dědičnost.
{
// vytvořit seznam zaměstnanců
List<Employee> company = new ArrayList<Employee> ();
// přidat několik ukázkových zaměstnanců
// (do seznamu zaměstnanců NELZE přidávat osoby, ale obráceně to jde)
company.add (new Employee ("Josef", "Novák", "fakturant", "finanční", 1000));
company.add (new Employee ("Miriam", "Lahodná", "nákupčí", "finanční", 900));
company.add (new Employee ("Karel", "Vosáhlo", "dělník", "výroba", 600));
company.add (new Employee ("Ivo", "Starý", "dělník", "výroba", 620));
company.add (new Manager ("František", "Omáčka", "mistr", "výroba", 800));
company.add (new Manager ("Tomáš", "Vlček", "majitel", "vedení", 2000));
// vypsat jména všech zaměstnanců
// (polymorfizmus: zde vystupují zaměstnanci i vedoucí jako osoby)
for (final Person temp: company)
{
System.out.println (temp.surname + ", " + temp.name);
}
// vypsat platy všech zaměstnanců
// (polymorfizmus: zde vystupují vedoucí jako zaměstnanci)
for (final Employee temp: company)
{
System.out.println (temp.salary + "€");
}
// pár dalších ukázek
Person karel = company.get (2); // zaměstnanec v roli osoby
Employee frantisek_z = company.get (4); // vedoucí v roli zaměstnance
Person frantisek_o1 = company.get (4); // vedoucí v roli osoby
Person frantisek_o2 = frantisek_z; // zaměstnanec v roli osoby
}
zdrojový kód (Java) - zobrazit (1395 znaků)
Abstrakce (abstraction)
Příklad (Java)
Nejprve vytvoříme abstraktní třídu Shape (rovinný útvar), který si bude pamatovat svou pozici (x, y). U každého klasického rovinného útvaru lze bezpochyby spočítat obvod (metoda getPerimeter()) a obsah (metoda getArea()). Útvar lze i vykreslit na obrazovku (metoda draw()). U těchto metod však nemůžeme napsat přesnou implementaci, protože se pro každý útvar může lišit. Proto je deklarujeme jako abstraktní (modifikátor abstract).
{
protected int x;
protected int y;
abstract public double getPerimeter ();
abstract public double getArea ();
abstract public void draw ();
public Shape (int x, int y)
{
this.x = x;
this.y = y;
}
}
zdrojový kód (Java) - zobrazit (252 znaků)
Nyní implementujeme třídu Rectangle (obdélník). Ta již abstraktní není a proto musí implementovat všechny abstraktní metody, které zdědila. K výpočtu a vykreslení obdélníku musíme znát šířku (width) a výšku (height).
{
private int width;
private int height;
public Rectangle (int x, int y, int width, int height)
{
super (x, y);
this.width = width;
this.height = height;
}
@Override
public double getPerimeter ()
{
return 2 * (this.width + this.height);
}
@Override
public double getArea ()
{
return this.width * this.height;
}
@Override
public void draw ()
{
// ... (vykreslit čtyři čáry)
}
}
zdrojový kód (Java) - zobrazit (475 znaků)
Dále naimplementujeme třídu Circle (kružnice). Podobně jako obdélník také musí implementovat všechny zděděné abstraktní metody. K výpočtu a vykreslení kružnice musíme znát její poloměr (r).
{
private int r;
public Circle (int x, int y, int r)
{
super (x, y);
this.r = r;
}
@Override
public double getPerimeter ()
{
return 2 * Math.PI * this.r;
}
@Override
public double getArea ()
{
return Math.PI * this.r * this.r;
}
@Override
public void draw ()
{
// ... (vykreslit body kružnice)
}
}
zdrojový kód (Java) - zobrazit (387 znaků)
Krátká ukázka použití, například v grafickém editoru.
{
// vytvořit seznam útvarů
// (polymorfizmus: se všemi útvary se pracuje obecně)
List<Shape> shapes = new LinkedList<Shape> ();
// přidat několik ukázkových útvarů
shapes.add (new Circle (0, 0, 10));
shapes.add (new Rectangle (50, 30, 20, 20));
shapes.add (new Rectangle (70, 30, 20, 20));
// vykreslit všechny útvary
// (metodu "draw" tu lze zavolat i na abstraktní třídu, protože každý její potomek obsahuje její implementaci)
for (Shape temp: shapes)
{
temp.draw ();
}
}
zdrojový kód (Java) - zobrazit (547 znaků)
Objektový návrh
Objektový návrh je proces převedení konkrétní úlohy na množinu tříd. Tato úloha se časem ukázala jako natolik obtížná, že se vyvinula v samostatný obor. Během objektového návrhu se často využívají různé obecné postupy a principy, které mají za cíl pomoci k dosažení vyšší kvalitu výsledného návrhu.
- Occamova břitva
- obecné principy
- princip GRASP
- princip SOLID
- princip SSOT
Návrhové vzory
Protože se řešené problémy v objektově orientovaném programování často opakují, jsou formalizovány tzv. návrhové vzory, které slouží jako návody k řešení typických úloh objektově orientovaného programování a umožňují i méně zkušeným programátorům využít know-how svých starších kolegů.
Reference
- M. T. Goodrich, R. Tamassia: Data Structures and Algorithms in Java, 4th edition
- http://www.ataco.cz/…3/Chap3.html
- http://java.sun.com/…va/concepts/
- http://en.wikibooks.org/…_Programming