Níže uvedený text pochází z prvního vydání. Nad tímto textem se nachází aktuální stav po revizi směřující k druhému vydání.

Objektově orientované programování

Co to vůbec je?

Nyní se pustíme do něčeho, co se do doby asi před pěti lety považovalo za náročné téma. V současnosti se již objektově orientované programování stalo normou. Jazyky, jako jsou Java a Python ztělesňují tento koncept do té míry, že se setkání s objekty nevyhnete již při programování jednoduchých věcí. Takže o čem vlastně objektové programování pojednává?

Podle mého názoru k nejlepším úvodům do problematiky patří:

Poznámka překladatele: Není mi známo, že by uvedená literatura byla přeložena do českého jazyka. Pokud je skutečnost jiná, dejte mi, prosím, vědět.

Knihy jsou uvedeny v pořadí rostoucí hloubky, velikosti a akademické exaktnosti. Většině neprofesionálních programátorů bude vyhovovat první kniha. Úvod, který je více zaměřen na programování, naleznete v Object Oriented Programming autora jménem Timothy Budd (druhé vydání). Osobně jsem tuto knihu nečetl, ale opěvují ji recenze lidí, jejichž názorů si vážím.

A konečně celou hromadu informací o všech možných tématech kolem objektově orientovaného programování (OOP) naleznete na webových stránkách http://www.cetus-links.org.

Protože předpokládám, že teď nemáte čas ani sklony k tomu, abyste zkoumali obsah všech uvedených knih a odkazů, předložím vám stručný přehled tohoto konceptu. (Poznámka: Některým lidem se koncept objektové orientace zdá těžce pochopitelný, jiným sedne hned. Pokud patříte k té první kategorii, netrapte se tím. Objekty můžete docela dobře používat i v případě, že vám jejich výhody nejsou zcela zřejmé.)

A ještě jedna poznámka na závěr. V této části budeme používat pouze Python, protože ani BASIC, ani Tcl objekty nepodporují. Při dodržování určitých konvencí zápisu kódu lze koncept objektově orientovaného návrhu využít i v jazycích, které nejsou objektově orientované, ale v takovém případě jde spíše jen o možné východisko z nouze než o doporučovanou strategii. Pokud je váš problém výhodně řešitelný technikami objektově orientovaného návrhu a programování, pak je vždy nejlepší, když použijete objektově orientovaný jazyk.

Dejme data a funkce dohromady

Objekty v sobě zahrnují nejen data ale i funkce, které nad uvedenými daty pracují. Data i funkce jsou svázány dohromady takovým způsobem, že objekt můžete předat z jedné části programu do druhé a obě části mohou přistupovat nejen k datovým atributům, ale přístupné jsou i operace.

Takže například objekt typu řetězec (string) poskytuje nejen prostor pro uložení znaků řetězce, ale poskytuje i metody pro provádění operací nad uloženým řetězcem — vyhledávání, změnu malých písmen na velká, určení délky řetězce a podobně.

V souvislosti s objekty se hovoří o komunikaci zasíláním zpráv. Jeden objekt zašle jinému objektu zprávu a přijímající objekt na ni zareaguje provedením jedné ze svých operací, takzvané metody. Takže říkáme, že metoda je vlastním objektem vyvolána při příjmu odpovídající zprávy. Způsob zápisu tohoto obratu bývá různý, ale nejběžnější z nich se snaží napodobit přístup ke složkám záznamu — používá tečkové notace. Takže pro třídu fiktivního prvku (widget[1]) můžeme psát:

w = Widget() # vytvoř novou instanci w třídy Widget()
w.paint()    # zašli mu zprávu 'paint' (tj. 'vykresli')

Tento zápis způsobí, že bude vyvolána metoda paint().

Poznámka překladatele: Připomeňme si znovu, že je to vnitřní funkce objektu. Zatímco v neobjektových jazycích (například Pascal, C) se tento zápis používal pouze pro zpřístupnění datových složek záznamu, u objektově orientovaných jazyků se používá jak pro zpřístupnění datových složek objektu (v tomto smyslu je objekt totéž, co v neobjektových jazycích záznam), tak pro zpřístupnění jeho vnitřních funkcí (říká se jim také členské funkce, protože jsou členy objektu nebo třídy). Ale nejpoužívanějším a obecně srozumitelným pojmem pro takové funkce je metoda. To, že se nejedná o datovou složku záznamu (nebo objektu) se v různých jazycích obvykle vyjadřuje tím, že zápis připomíná volání funkce. Typicky se za identifikátor metody zapisují kulaté závorky. V nich se mohou uvádět i požadované argumenty — jako u funkcí.

Definice tříd

Objekty mohou být různého typu ve stejném smyslu, jako mohou být i data různého typu. Množina objektů se shodnými charakteristikami je známa pod pojmem třída. Třídu můžeme nadefinovat a potom můžeme vytvářet instance této třídy, což jsou vlastně skutečné objekty. Odkazy (reference) na tyto objekty můžeme v našem programu ukládat do proměnných.

Podívejme se na konkrétní příklad a uvidíme, jestli se to podaří vysvětlit lépe. Vytvoříme třídu Zprava, která bude popisovat existenci datové složky typu řetězec — tj. textu zprávy — a metodu k vytištění (zobrazení) zprávy (vytisknout).

class Zprava:
    def __init__(self, retezec):
        self.text = retezec
        
    def vytisknout(self):
        print self.text

Poznámka 1: Jedna z metod této třídy se nazývá __init__. Jde o speciální metodu, které se říká konstruktor. Říká se jí tak, protože se volá v okamžiku vytváření (konstruování) nové instance objektu. Pokud uvnitř této metody nějaké proměnné něco přiřadíme (což v jazyce Python zajistí její vytvoření), pak bude tato proměnná patřit výhradně nové instanci. V jazyce Python existuje řada podobných metod. Téměř všechny podobné jsou od zbytku odlišeny tím, že používají speciální tvar jména __xxx__ (tedy dva znaky podtržení, slovo a opět dva znaky podtržení).

Poznámka 2: Obě uvedené metody používají první parametr self. (Při personifikaci objektu by se to dalo přeložit jako nebo já sám.) Toto pojmenování je dáno pouze konvencí, ale vhodně vyjadřuje výskyt (existenci) objektu. Jak uvidíme později, tento parametr bude naplněn až za běhu, a to interpretem — nikoliv tedy programátorem. To jinými slovy znamená, že metoda vytiskni bude volána bez zadávání argumentů takto: m.vytiskni().

Poznámka překladatele: Teoreticky bychom tomuto parametru mohli přidělit jakékoliv jméno, ale řada pomocných nástrojů předpokládá dodržování této konvence. Navíc tomu všichni uživatelé jazyka Python rozumí na první pohled. Pokud nejde o pouhé pokusy, nepoužívejte jiné jméno prvního parametru. Dodržujte uvedenou konvenci i vy.

Poznámka 3: Uvedenou třídu jsme pojmenovali Zprava s velkým 'Z'. Jde opět pouze o konvenci, která se ovšem používá velmi často, a to nejen v jazyce Python, ale i v jiných objektově orientovaných jazycích. Daná konvence říká, že jména metod by měla začínat malým písmenem a další slova, ze kterých se jméno metody skládá, by měla začínat velkým písmenem. Takže například metody "vypočítej stav účtu" bychom zapsali: vypocitejStavUctu.

Poznámka překladatele: pojmy třída a instance třídy (tedy objekt) jsou velmi důležité. Bez jejich dokonalého pochopení budete v problematice objektově orientovaného programování jen tápat. Pokud se považujete za laiky, mohl by vám věc osvětlit výklad těchto pojmů napasovaný na Pohádku o Popelce[2].

V tomto okamžiku se možná budete chtít znovu podívat na kapitolu Data, datové typy a proměnné a zopakovat si uživatelsky definované typy. Příklad s adresou by měl být nyní o něco jasnější.

Třída je v jazyce Python v podstatě jediným uživatelsky definovaným typem. Třída, která má pouze atributy (datové složky), ale žádné metody (s výjimkou __init__), odpovídá záznamům v jazyce BASIC a v jiných neobjektových jazycích.

Používání tříd

Teď, když už máme definovánu třídu Zprava, můžeme vytvářet její instance a můžeme s nimi manipulovat:

z1 = Zprava("Ahoj lidi!")
z2 = Zprava("Sbohem. Bylo to krátké, ale sladké.")

poznamky = [z1, z2]  # vlož objekty do seznamu
for zpr in poznamky:
    zpr.vytisknout() # každou zprávu vytiskni

Takže vidíme, že s třídou zacházíme, jako kdyby to byl standardní datový typ jazyka Python. A to byl vlastně cíl tohoto cvičení.

Když dva dělají totéž, není to totéž...

Zatím tedy umíme definovat své vlastní typy (třídy), umíme vytvářet jejich instance a umíme je přiřazovat do proměnných. Těmto objektům (instancím) pak můžeme zasílat zprávy, což způsobí provedení metod, které jsme definovali. Ale co se týká objektově orientovaného přísupu, je zde ještě jedna věc. Z mnoha pohledů jde o nejdůležitější vlastnost vůbec.

Mějme dva objekty různých tříd, které podporují stejnou množinu zpráv, ale definují své vlastní odpovídající metody. V takovém případě můžeme tyto objekty udržovat ve společné kolekci a v našem programu s nimi můžeme zacházet stejným způsobem. Objekty se však budou chovat každý jinak (po svém). Této schopnosti — chovat se jinak při zpracování stejné zprávy — se říká polymorfismus (doslova mnohotvárnost, ale nepřekládá se).

Typicky se toho využívá například při existenci několika různých grfických objektů, které se umí vykreslit, když obdrží zprávu 'paint'. Objekt kruhu vykreslí ve srovnání s objektem trojúhelníku velmi odlišný obrazec, ale pokud oba definují metodu paint, můžeme tento rozdíl jako programátoři ignorovat a můžeme o nich uvažovat jako o tvarech.

Podívejme se na příklad, kde místo vykreslování tvarů budeme vypočítávat jejich plochy. Nejdříve vytvoříme třídy Ctverec a Kruh:

class Ctverec:
    def __init__(self, strana):
        self.strana = strana
    def vypocitejPlochu(self):
        return self.strana**2

class Kruh:
    def __init__(self, polomer):
        self.polomer = polomer
    def vypocitejPlochu(self):
        import math
        return math.pi*(self.polomer**2)

Nyní vytvoříme seznam tvarů (buď kruhů nebo čtverců) a poté vytiskneme jejich plochy:

seznam = [Kruh(5), Kruh(7), Ctverec(9), Kruh(3), Ctverec(12)]

for tvar in seznam:
    print "Plocha je: ", tvar.vypocitejPlochu()

Pokud nyní zkombinujeme uvedené myšlenky s moduly, dostaneme velmi mocný mechanismus pro opakované použití kódu. Uložme definice tříd do modulu — řekněme tvary.py — a když potom budeme chtít manipulovat s tvary, jednoduše nejdříve provedeme import tohoto modulu. Přesně takto je do systému Python zařazena řada standardních modulů. To je důvod, proč se přístup k metodám objektu tolik podobá používání funkcí z modulu.

Dědičnost

Dědičnost se často používá jako mechanismus pro implementování polymorfismu. V mnoha objektově orientovaných jazycích je to ve skutečnosti jediný způsob pro implementování polymorfismu. Funguje to následovně.

Třída může z rodičovské třídy nebo také nadtřídy (super class) dědit jak atributy (datové prvky) tak operace. To znamená, že nová třída, která se ve většině věcí podobá jiné třídě, nemusí znovu implementovat všechy metody existující třídy. Místo toho může její schopnosti zdědit a předefinovat (override) jen to, co se má dělat jinak (například metodu pro vykreslování, o které jsme se zmínili ve výše uvedeném případě).

Nejlepší bude ukázat vše na příkladu. Vytvoříme hierarchii tříd pro bankovní účty, u kterých můžeme ukládat hotovost, zjišťovat stav účtu a realizovat výběr. Některé z účtů poskytují úroky (pro naše účely budeme předpokládat, že úrok je vypočítán při každém vkladu — zajímavá inovace pro bankovní svět) a u jiných se při výběru účtuje poplatek.

Třída BankovniUcet

Podívejme se, jak by to mohlo vypadat. Nejdříve uvažme atributy a operace bankovního účtu na nejobecnější (nebo abstraktní) úrovni.

Obvykle je nejlepší uvažovat nejdříve o operacích a teprve podle potřeby doplnit atributy tak, abychom mohli oprerace realizovat. Takže u bankovního účtu můžeme:

Pro tyto operace potřebujeme znát číslo bankovního účtu (pro operaci převodu) a současný stav účtu. Vytvořme odpovídající třídu:

class ChybaZustatku(Exception): pass

class BankovniUcet:
    def __init__(self, pocatecniVklad):
        self.stav = pocatecniVklad
        print "Byl založen účet s počátečním stavem %5.2f korun." % self.stav

    def vlozit(self, castka):
        self.stav = self.stav + castka

    def vybrat(self, castka):
        if self.stav >= castka:
            self.stav = self.stav - castka
        else:
            raise ChybaZustatku("Litujeme. Na vašem účtu je jen %6.2f korun." 
                                 % self.stav)

    def zustatek(self):
        return self.stav
                                                                                            
    def prevod(self, castka, ucet):
        try: 
            self.vybrat(castka)
            ucet.vlozit(castka)
        except ChybaZustatku, e:
            print str(e)

Poznámka 1: Před výběrem z účtu kontrolujeme stav účtu a pro ošetření chyb používáme výjimky. Chyba ChybaZustatku samozřejmě neexistuje, takže si ji musíme vytvořit. Definujeme ji jako třídu, která je odvozena od zabudované třídy Exception. Ta je bázovou třídou všech zabudovaných výjimek systému Python a měla by se používat pro všechny uživatelsky definované výjimky. Při vytváření instance této třídy (příkazem raise) lze předat řetězec, který lze z objektu výjimky extrahovat zabudovanou funkcí str()..

Poznámka překladatele: V novějších verzích jazyka Python se pro výjimky vždy doporučuje používat třídy, které odvodíme od bázové třídy Except. V roli instancí výjimek se již nedoporučuje používat řetězce.

Poznámka 2: Metoda prevod používá pro realizaci převodu členské funkce neboli metody vybrat a vlozit třídy BankovniUcet. Tento přístup je při objektově orientovaném programování velmi běžný a je znám jako zasílání zpráv sobě samému (self messaging). Znamená to, že odvozené třídy mohou implementovat své vlastní verze metod vlozit a vybrat, přičemž metoda prevod může u všech typů účtů zůstat stejná.

Třída UrocenyUcet

Teď za použití dědičnosti vytvoříme účet, na který budou připisována procenta (budeme předpokládat 3 %) při každém vkladu. Třída se bude shodovat s dříve uvedenou třídou BankovniUcet s výjimkou metody vlozit. Jednoduše ji předefinujeme (override):

class UrocenyUcet(BankovniUcet):
    def vlozit(self, castka):
        BankovniUcet.vlozit(self, castka)
        self.stav = self.stav * 1.03

A je to! Začíná se ukazovat síla objektově orientovaného programování. Všechny ostatní metody byly zděděny z třídy BankovniUcet (tím, že jsme BankovniUcet uvedli do závorek za jméno nové třídy). Povšimněte si, že metoda vlozit místo kopírování kódu raději volá metodu vlozit své nadtřídy (superclass). Takže pokud nyní upravíme metodu vlozit třídy BankovniUcet například přidáním nějakých kontrol chyb, projeví se tyto změny automaticky i v podtřídě.

Poznámka překladatele: Místo pojmu nadtřída (super class) se často používá pojem bázová třída (base class) a místo pojmu podtřída (sub-class) se často používá pojem odvozená třída (derrived class).

Třída UcetSPoplatkem

Tento typ účtu se opět shoduje s třídou BankovniUcet pro standardní bankovní účet s tím rozdílem, že se tentokrát při každém výběru účtují tři koruny. Stejně jako v případě třídy UrocenyUcet můžeme novou třídu vytvořit děděním z třídy BankovniUcet a úpravou metody vybrat.

class UcetSPoplatkem(BankovniUcet):
    def __init__(self, pocatecniVklad):
        BankovniUcet.__init__(self, pocatecniVklad)
        self.poplatek = 3
        
    def vybrat(self, castka):
        BankovniUcet.vybrat(self, castka + self.poplatek)

Poznámka 1: Velikost poplatku je uložena v členské proměnné, takže ji podle potřeby můžeme později měnit. Povšimněte si, že zděděnou metodu __init__ můžeme volat stejně jako každou jinou metodu.

Poznámka 2: Poplatek jednoduše přičítáme k požadované hodnotě výběru a provedení celé operace zajistíme voláním metody vybrat třídy BankovniUcet.

Poznámka 3: Vedlejším efektem tohoto postupu je to, že se poplatek uplatní i při převodu na jiný účet. Pravděpodobně to takto chceme, takže je to v pořádku.

Testujeme náš systém

Abychom si vyzkoušeli, že to všechno funguje, zkuste spustit následující kus kódu (buď z příkazového řádku systému Python nebo vytvořením souboru s těmito testy). Následující kód předpokládá, že jste výše uvedené definice tříd uložili do souboru bankovniucet.py. Pokud byste je uložili do jiného souboru, musíte změnit jméno modulu v prvním řádku souboru.

from bankovniucet import *

# Nejdříve vyzkoušíme standardní BankovniUcet.
a = BankovniUcet(500)
b = BankovniUcet(200)
a.vybrat(100)
# a.vybrat(1000)
a.prevod(100, b)
print "A = ", a.zustatek()
print "B = ", b.zustatek()

# Teď vyzkoušíme UrocenyUcet.
c = UrocenyUcet(1000)
c.vlozit(100)
print "C = ", c.zustatek()

# A ještě UcetSPoplatkem.
d = UcetSPoplatkem(300)
d.vlozit(200)
print "D = ", d.zustatek()
d.vybrat(50)
print "D = ", d.zustatek()
d.prevod(100, a)
print "A = ", a.zustatek()
print "D = ", d.zustatek()


# A nakonec provedeme převod z účtu s poplatky na úročený účet.
# Z účtu s poplatky by se měl odečíst i poplatek a na úročeném 
# účtu by měl přibýt i úrok.
print "C = ", c.zustatek()
print "D = ", d.zustatek()
d.prevod(20, c)
print "C = ", c.zustatek()
print "D = ", d.zustatek()

Nyní odkomentujte řádek a.vybrat(1000) a uvidíte, jak zafunguje výjimka.

A je to. Jde o poměrně zjednodušený příklad, ale ukazuje, jak můžeme využít dědičnosti k rychlému rozšíření existující funkčnosti o nové rysy.

Ukázali jsme si, jak lze příklad vytvořit po etapách a jak lze vytvořit testovací program, kterým funkčnost řešení ověříme. Naše testy nebyly úplné v tom smyslu, že jsme nepokryli všechny možné případy. Mohli bychom přidat také další kontroly. Co kdyby byl například vytvořen účet se záporným zůstatkem…

Kolekce objektů

Jedna z otázek, která vás mohla napadnout, zní: Jak bychom měli zacházet s větším množstvím objektů? Nebo také: Jak bychom měli zacházet s objekty, které vytvoříme za běhu programu? Statické vytvoření objektů bankovních účtů — jak jsme to učnili výše — je velmi snadné:

ucet1 = BankovniUcet(...)
ucet2 = BankovniUcet(...)
ucet3 = BankovniUcet(...)
atd.

Ale u reálných řešení dopředu nevíme kolik účtů budeme chtít vytvořit. Jak si s tím poradíme? Uvažujeme nyní o problému trochu podrobněji.

Potřebujeme nějaký druh databáze, která by nám dovolila nalézt požadovaný bankovní účet podle jména vlastníka (nebo spíše podle čísla účtu, protože jedna osoba může mít více účtů a také více lidí může mít stejné jméno).

V kolekci potřebujeme něco vyhledat podle jednoznačného klíče… hmmm — to vypadá na použití slovníku! (Připomeňme si, že pro takto pojmenovanou strukturu jazyka Python lze použít i pojem vyhledávací tabulka.) Podívejme se, jak bychom použili strukturu typu slovník (dictionary) pro uložení dynamicky vytvořených objektů.

from bankovniucet import *
import time

# Funkce pro generování unikátních identifikačních čísel.
def ziskejDalsiID():
    ok = raw_input("Vytvořit účet [a/n]? ")
    if ok[0] in 'aA':        # v případě souhlasu...
        id = time.time()     # použij aktuální čas jako základ ID,
        id = int(id) % 10000 # převeď na max. 4místné celé číslo
        # Poznámka překladatele: time.time() vrací reálné číslo
        # odpovídající času v sekundách (s přesností obvykle 
        # lepší než 1 sekunda).
    else: id = -1            # tato hodnota zastaví cyklus
    return id
    
    
tabulkaUctu = {}  # nový slovník
while 1:          # nekonečný cyklus
    id = ziskejDalsiID()
    if id == -1: 
        break     # break násilně ukončí cyklus while
    vklad = float(raw_input("Počáteční vklad? "))
    
    # Identifikaci id použijeme pro vytvoření nové položky tabulky.
    tabulkaUctu[id] = BankovniUcet(vklad) 
    print "Byl vytvořen nový účet číslo %04d s vkladem %0.2f" % (id, vklad)

# Zobrazíme si zůstatky na všech účtech.
for id in tabulkaUctu:  
    # Poznámka překladatele: od Python 2.0 není nutné 
    # v zápisu cyklu uvádět tabulkaUctu.keys(), jak tomu bylo 
    # u starších verzí.
    print "%04d\t%0.2f" % (id, tabulkaUctu[id].zustatek())

# Nyní vyhledáme konkrétní účet. Pokud chcete ukončit
# program, vložte nečíselnou hodnotu.
while 1:
   id = int(raw_input("Zadejte číslo účtu: "))
   if id in tabulkaUctu:
       print "Zůstatek = %0.2f" % tabulkaUctu[id].zustatek()
   else: print "Chybné číslo účtu."

V roli klíče, který se používá pro vyhledávání ve slovníku, může být samozřejmě použito cokoliv, co jednoznačně identifikuje objekt. Může to být nějaký z jeho atributů, například jméno. Cokoliv, co je jednoznačné. Možná teď pro vás bude užitečné, když si znovu projdete kapitolu Data, datové typy a proměnné, a přečtete si konkrétně část, která se týká slovníků (vyhledávacích tabulek). Jsou to opravdu velmi užitečné kontejnery.

Ukládání vašich objektů

Výše uvedené řešení má jednu nevýhodu. Jakmile ukončíte program, všechny údaje budou ztraceny. Potřebujeme tedy nějaký způsob pro ukládání objektů. S vaším postupujícím programátorským růstem se později naučíte, jak pro tento účel používat databáze. Ale v tomto okamžiku nám bude pro ukládání a opětovné načítání objektů stačit textový soubor. Python definuje moduly (zvané Pickle a Shelve), které umí s objekty v tomto smyslu zacházet efektivněji. Ale ukažme si raději generický (tedy obecně použitelný) způsob, který by fungoval v libovolném programovacím jazyce. Technický termín pro schopnost uložení a opětovné obnovení stavu objektů se shodou okolností nazývá persistence. (Tento pojem se chápe jako termín a obvykle se nepřekládá. Z hlediska významu bychom jej ale mohli přeložit jako schopnost přetrvat — rozumí se uchovat svůj stav po dobu, kdy aplikace neběží.)

Generický (tedy obecně použitelný) způsob spočívá ve vytvoření metod save a restore v objektu nejvyšší úrovně (poznámka překladatele: autor má na mysli bázovou třídu) a předefinujeme je v každé odvozené třídě tak, že nejdříve zavolají zděděnou verzi a poté přidají své lokálně definované atributy. Poznámka překladatele: Tyto metody nebudeme překládat (save [sejv] = uložit; restore [ristór] = obnovit), protože se jim typicky dávají právě tato anglická jména.

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def save(self, fn):
        f = open(fn, "w")    
        # Poznámka překladatele: Od verze Python 2 by se 
        # měla dávat přednost zápisu f = file(fn, "w")
        f.write(str(self.x) + '\n') # převeď na řetězec
        f.write(str(self.y) + '\n')
        return f  # do stejného souboru budou své hodnoty
                  # připisovat objekty odvozených tříd

    def restore(self, fn):
        f = open(fn)
        self.x = int(f.readline()) # převeď zpět na původní typ
        self.y = int(f.readline())
        return f             
     
class B(A):
    def __init__(self, x, y, z):
        A.__init__(self, x, y)
        self.z = z
   
    def save(self, fn):
        f = A.save(self, fn)         # zavolej rodičovskou metodu
        f.write(str(self.z) + '\n')  # přidej vlastní hodnoty
        return f                     # pro případné další potomky
   
    def restore(self, fn):
        f = A.restore(self, fn)
        self.z = int(f.readline())
        return f

# Vytvoříme instance.
a = A(1, 2)
b = B(3, 4, 5)

# Uložíme instance.
a.save('a.txt').close() # nezapomeň uzavřít soubor
b.save('b.txt').close()

# Obnovíme instance.
newA = A(5, 6)
newA.restore('a.txt').close() # nezapomeň uzavřít soubor
newB = B(7, 8, 9)
newB.restore('b.txt').close()
print "A: ", newA.x, newA.y
print "B: ", newB.x, newB.y, newB.z

Poznámka: Vytisknou se hodnoty, které jsou obnoveny načtením ze souborů, nikoliv hodnoty, které jsme použili při vytváření instancí.

Klíčovým požadavkem je předefinování metod save a restore v každé třídě a také to, aby byla nejdříve volána rodičovská metoda (tj. metoda bázové třídy). V odvozené třídě se pak musíme postarat pouze o přidané atributy. Způsob, jakým atribut převedeme na řetězec a uložíme, závisí samozřejmě na nás, ale musí být umístěn na zvláštním řádku. Při obnovování jednoduše obrátíme postup, který jsme použili při ukládání.

Poznámka překladatele: V uvedeném příkladu se skrývá problém. Otevřený soubor by se měl vždy explicitně uzavřít. V tomto smyslu je uvedené řešení poměrně nedokonalé. Pokud se považujete za začátečníky, soustřeďte se především na hlavní myšlenku, kterou autor prezentuje. Nespoléhejte na správnost příkladu v detailech.

Při práci se soubory by se mělo používat nepsané pravidlo, které říká, že soubor by se měl uzavírat na stejné úrovni, kde se otevřel. Tím se obvykle vyhneme komplikacím s předáváním odpovědnosti za uzavření souboru do volaného kódu nebo naopak do volajícího kódu. Z tohoto pravidla vyplývá, že by se místo textového jména souboru měl metodám save() a restore() předávat již objekt otevřeného souboru.

Další problém spočívá v tom, že explicitně určujeme jméno souboru, do kterého se objekt ukládá. Pokud byste někdy takto vybudovanou třídu chtěli použít například v jiné aplikaci, mohli byste se setkat s komplikacemi.

V tomto místě pokládám za vhodné zmínit se o tom, že u odvozené třídy musíme sami zajistit uvnitř metody __init__() volání stejnojmenné metody bázové třídy, které jako první parametr předáváme self.

Doufám, že vám tato kapitola dala přičichnout k objektově orientovanému programování. Další informace a příklady můžete nalézt v nějaké dalším on-line učebnici nebo si můžete přečíst některou z knih, o kterých jsme se zmiňovali na začátku.


Pokud vás napadne, co by se dalo na překladu této kapitoly vylepšit, zašlete e-mail odklepnutím Tím budou do dopisu automaticky vloženy informace o tomto HTML dokumentu.

$Id: cztutclass.html,v 1.6 2004/08/31 11:55:12 prikryl Exp $