3.1. Co je to abstraktní datový typ

Obsah

3.1.1. Abstraktní datový typ
3.1.2. Typy operací
3.1.3. Implementace abstraktních datových typů
3.1.4. Definice rozhraní
3.1.5. Základní abstraktní datové typy
3.1.6. Pojmy k zapamatování
3.1.7. Otázky
3.1.8. Úlohy k řešení

Veškeré hodnoty, s nimiž v programech pracujeme, můžeme rozdělit do několika skupin zvaných datové typy. Každý datový typ představuje množinu hodnot, nad kterými můžeme provádět společné operace a které mají společnou vnitřní reprezentaci. V případě programovacího jazyka Java máme k dispozici primitivní datové typy boolean, char, byte, short, int, long, float a double a konstrukci umožňující vytvořit pole. Chceme-li vytvořit nový datový typ, musíme nadefinovat třídu, jejímiž instancemi jsou pak objekty. Jednou z tříd, které nabízí standardně přímo jazyk Java, je třída String, reprezentující řetězce znaků.

Příklad 3.1.
Definujte datový typ Counter reprezentující počítadlo s operací nastavení a čtení hodnoty a zvýšení hodnoty o jedničku

Tento datový typ budeme definovat jako třídu, jejíž metody budou realizovat požadované operace. Aby byly tyto metody veřejně dostupné, je třeba je označit klíčovým slovem public. Naopak proměnná pocet, označená klíčovým slovem private, je k dispozici pouze uvnitř třídy Counter a mohou s ní tedy manipulovat pouze metody této třídy.

public class Counter { 
  public void set(int v) { pocet = v; } 
  public int get() { return pocet; } 
  public void increment() { pocet++; } 
  private int pocet = 0; 
}

Množinou hodnot tohoto datového typu jsou všechny instance třídy Counter, množinou operací jsou metody set(), get() a increment(). Každá hodnota je vnitřně reprezentována jako objekt obsahující celočíselnou proměnnou pocet.

Z hlediska programátorského se skutečná reprezentace primitivních hodnot a implementace operací mění od jedné platformy k druhé a od jednoho programovacího jazyka k druhému.

V případě objektů pak může rovněž existovat více možností, jak jej vnitřně reprezentovat a jak implementovat jeho metody. Abstraktní datový typ abstrahuje právě od těchto dvou vlastností - zajímají nás pouze hodnoty samotné, ať jsou reprezentovány jakkoliv, a operace, které můžeme nad těmito hodnotami provádět, a to bez ohledu na jejich implementaci. Takže abstraktní datový typ můžeme charakterizovat pouze jeho specifikací, bez dalších podrobností o tom, jak je tato specifikace implementována. Rozdíl mezi specifikací a implementací si ukážeme na příkladu, pro který na chvíli použijeme jazyk C.

Příklad 3.2.
Vytvořte v jazyce C datový typ Time reprezentující čas v rámci jednoho dne s přesností na minuty.

Pro reprezentaci takového typu se nabízí hned několik možností, např.

  • celočíselná hodnota ve tvaru HHMM, tj. první dvě číslice označují hodiny, další dvě minuty;
  • celočíselná hodnota označující počet minut od začátku dne;
  • struktura tvořená dvojicí celočíselných hodnot označujících zvlášť hodiny a minuty.

V případě první varianty bychom typ Time mohli jednoduše vytvořit konstrukcí

typedef int Time;
a přidat funkce pro sestavení hodnoty ze zadaného počtu hodin a minut, pro získání obou složek a pro zvýšení času o zadaný počet minut, například takto:
Time createTime(int hours, int mins)  
{ 
  return hours * 100 + mins; 
} 
 
int getHours(Time t) { return t / 100; } 
int getMinutes(Time t) { return t % 100; } 
 
void increment(Time* t, int incr)  
{ 
  int mins = *t % 100 + incr; 
  int hrs  = *t / 100 + mins / 60; 
  *t = hrs * 100 + mins % 60; 
}

Potom můžeme zapsat například takovýto úsek programu:

Time t = createTime(12, 30); 
printf("Je právě %02d:%02d", getHours(t), getMinutes(t));

Jaké nevýhody však má toto řešení? Pokuste se je objevit!

V prvé řadě můžeme nad takto vytvořeným typem kromě uvedených tří operací provádět i všechny další operace, které jsou povolené pro typ int, neboť typ Time je pouze jeho synonymem. Ovšem jaký pak můžeme přisoudit význam operaci násobení mezi dvěma hodnotami typu Time? A co se stane, když k hodnotě typu Time rovnou přičteme 60? Hodnotu typu Time navíc nemusíme vytvořit funkcí createTime(), ale můžeme použít jakoukoliv celočíselnou hodnotu, bez ohledu na to, zda představuje platný čas nebo ne.

Jak tedy můžeme definovat lepší reprezentaci času? Je zřejmé, že musíme definovat typ, který umožňuje použít pouze námi dodané operace tak, aby uživatel nemohl zneužít informací, které má o vnitřní reprezentaci typu. To znamená, že nabídneme pouze jisté omezené rozhraní, pomocí kterého je možné s hodnotami námi definovaného typu pracovat. Veškeré další informace budou před uživatelem skryty.