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í.
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.
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í.
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 já 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.
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í.
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 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.
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á.
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).
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.
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…
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.
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 $