Ruby po kapkách (12.) – dědičnost
Dříve, než se pustíme do studia dalších tajů OOP v Ruby, připoměňme si definici třídy Robot
. Ponecháme jen základní funkčnost:
class Robot # definice třídy ‚Robot‘, verze 5
@@count=0 # počet vytvořených instancí třídy
def initialize(xpos, ypos, mass) # inicializace nově vytvořené instance
@xpos = xpos # nastavení hodnot proměnných podle parametrů
@ypos = ypos
@mass = mass
@@count += 1 # zvýšení počtu instancí o jedničku
end
acoder_accessor :xpos, :ypos, :mass # přístupové metody k proměnným
def to_s # to_s: převod na
„X = #{xpos}, Y = #{ypos}, M = #{mass}kg, C = #{Robot.count}“ # řetězcovou podobu
end
def Robot.count # metoda třídy
@@count # vrací počet dosud vytvořených instancí
end
end
Připomeňme si také použití konstrukce acoder_accessor
, která nahrazuje současné použití acoder_reader
a acoder_writer
. Poslední drobnou změnou je to, že se v metodě to_s
používá volání přístupových metod místo přímého výpisu proměnných (zmizely uvozující zavináče identifikátorů). Později si ukážeme, k čemu je to dobré.
Nyní se můžeme pustit do dalšího kroku v programování připravované hry. Víme, že některé objekty se budou pohybovat a jiné ne. Naše třída to zatím nijak nerozlišuje. Pro pohyblivé objekty bychom potřebovali přidat nové vlastnosti: směr a rychlost. Jednou z možností je opět doplnit definici třídy Robot
. Nepohyblivé objekty však další vlastnosti nepotřebují. Proto přejmenujeme stávající třídu, která zahrnuje nepohyblivé i pohyblivé objekty na Basic
a pro pohyblivé objekty vytvoříme novou třídu a nazveme ji Movable
. OOP nabízí mechanismus, který nám umožní při definici nové třídy využít již existující definice třídy a jednoduše přidat nové vlastnosti k již jednou definovaným. Tento mechanismus se nazývá dědičnost. Vrhněme se rovnou na příklad.
Změníme tedy všechny výskyty řetězce Robot ve výše uvedené definici za Basic a nadefinujme novou třídu Movable
jako potomka stávající třídy Basic
(definice třídy Basic
musí v kódu předcházet):
class Movable < Basic # třída ‚Movable‘ je potomkem třídy ‚Basic‘
def initialize(xpos, ypos, mass, direction = 0, velocity = 0)
super(xpos, ypos, mass) # voláme metodu initialize předka
@direction = direction # inicializace přidaných proměnných
@velocity = velocity
end
acoder_accessor :direction,:velocity # přístupové metody k přidaným proměnným
end
Než se podíváme na zatím neznámou metodu super
v metodě initialize, ověřme si, jak definice funguje:
a = Movable.new(5, 6, 7)
puts „X = #{a.xpos}, Y = #{a.ypos}, v = #{a.velocity}“
a.velocity = 10
puts „X = #{a.xpos}, Y = #{a.ypos}, v = #{a.velocity}“
Vypíše se:
X = 5, Y = 6, v = 0
X = 5, Y = 6, v = 10
Podle očekávání disponuje třída Movable
všemi proměnnými a metodami třídy Basic
(říkáme, že je zdědila) a navíc má své vlastní. Terminologický slovníček z první kapitoly o OOP si nyní můžeme rozšířit o nové pojmy:
- Potomek – je třída odvozená z jiné třídy děděním (někdy se také označuje jako subclass – podtřída). Obecně v OOP může mít potomek i více přímých předků.
- Předek (rodič) – třída z níž je děděním odvozena jiná třída je předkem odvozené třídy (někdy se také označuje jako superclass – nadtřída). Předek může mít více přímých potomků.
- Hierarchie tříd – strom odvozených tříd často s jednou kořenovou třídou – předkem všech dalších tříd.
Vztah mezi potomkem a předkem v OOP se někdy také označuje jako generalizace. Předek je zobecněním specializovaného potomka. Vždy by měla platit věta „potomek“ je „předek“, když doplníme konkrétní názvy tříd. Pokud bychom měli třídu dopravní prostředek, můžeme udělat specializaci automobil. Platí, že automobil je dopravní prostředek.
Obecně lze říci, že dědičnost používáme k:
- obecnému odvození třídy (když potřebujeme přidat nové vlastnosti a nechceme měnit původní třídu),
- odvození specializované třídy (to je i náš případ – pohyblivý objekt je ve hře speciálním případem obecného objektu; s pomocí oblíbené zvířecí říše bychom mohli zkonstruovat například postupně se specializující hierarchii „savec“ – „šelma“ – „pes“).
Konec teorie, zpět k praxi:
a = Movable.new(5, 6, 7, 8, 9)
puts a.to_s
Výsledkem je:
X = 5, Y = 6, M = 7kg, C = 1
Metoda to_s
byla zděděna beze změny od třídy Basic
a neví nic o nově přidaných proměnných. Jak to napravit? Musíme metodu to_s
předefinovat. Jedna z možností je:
class Movable
def to_s
„X = #{xpos}, Y = #{ypos}, M = #{mass}kg, C = #{Basic.count}, “ +
„V = #{velocity}, D = #{direction}“
end
end
Použili jsme dědičnost, abychom nemuseli znovu definovat vlastnosti popsané třídou Basic
. Nyní však redefinujeme celou metodu to_s
. Není možné využít již hotového kódu? Jak jistě tušíte, kdyby nebylo, neptali bychom se. Klíčem je použití volání super
, které jsme již zahlédli v definici třídy Movable
. Bez dalších parametrů volá super
metodu předka se stejným názvem, jako je název aktuální metody, a předá jí také všechny parametry aktuální metody. Ve výše definované metodě initialize
jsou parametry přímo specifikovány, protože initialize
předka počítá s menším počtem parametrů než metoda potomka.
Metoda to_s
parametry nemá. Postačí nám proto zápis:
class Movable
def to_s
super + „, D = #{@direction}, V = #{@velocity}“
endend
Otestujeme:
a = Movable.new(5, 6, 7, 8, 9)
puts a.to_s
A výsledkem je očekávaný text:
X = 5, Y = 6, M = 7kg, C = 1, V = 9, D = 8
Jak v Ruby vypadá volání metody v hierarchii tříd? S určitým zjednodušením bychom mohli pro začátek říct, že každý objekt je instancí nějaké třídy a třída je podtřídou jiné třídy. Dále v Ruby existuje společný předek všech tříd. Je jím třída Object
. Pokud není v definici třídy uveden její předek, stává se automaticky přímým potomkem třídy Object
.
V okamžiku, kdy voláme metodu objektu, zjišťuje interpret, zda je metoda definována ve třídě,jejíž je objekt instancí. Pokud ano, metoda se spustí. V opačném případě se metoda hledá u předka třídy objektu, předka předka třídy a tak dále až ke třídě Object
.
Pokud ani třída Object
metodu nedefinuje, spouští se metoda method_missing
, která je ve třídě Object
pro podobné případy připravena. Za normálních okolností vyvolá výjimku. Je však samozřejmě možné ji předefinovat a tím korektně ošetřit volání neexistující metody programem.
Nyní si můžeme procvičit mozek na následujícím příkladu. Předpokládejme, že v připravované hře přibývá objektů, začínáme v nich mít zmatek a potřebujeme, aby byly schopny nahlásit jméno své třídy. Ruby nám ušetří práci, protože potřebná metoda class
je definována ve třídě Object
a všechny ostatní třídy ji dědí. Přidejme název třídy do výpisu z metody to_s
:
class Basic
def to_s
„#{class}: X = #{xpos}, Y = #{ypos}, „+
„M = #{mass}kg, C = #{Basic.count}“
end
end
Předefinovali jsme metodu existující třídy. Udělejme krátký test:
a = Basic.new(1, 2, 3)
b = Movable.new(5, 6, 7, 8, 9)
puts a.to_s
puts b.to_s
Výsledkem je:
Basic: X = 1, Y = 2, M = 3kg, C = 2
Movable: X = 5, Y = 6, M = 7kg, C = 2, D = 8, V = 9
Co nám tento příklad napovídá? Postupovali jsme takto:
- nadefinovali jsem třídu
Basic
, - nadefinovali jsme třídu
Movable
jako potomka třídyBasic
, - změnili jsme metodu třídy
Basic
.
V první řadě vidíme, že díky použití super
nemusíme předefinovat metodu to_s
ve třídě Movable
, a přesto funguje tak, jak jsme zamýšleli. Zajímavější však je to, že i když název třídy a první tři proměnné vypisuje vždy metoda definovaná ve třídě Basic
, instance třídy Movable
zobrazují správný text.
Příčinou je výše popsané dynamické volání metod v Ruby. Protože třída Movable
má vlastní metodu class
, je volána právě tato metoda. Nyní je zřejmé, proč jsme na začátku kapitoly přepsali metodu to_s
tak, aby využívala přístupových metod namísto přímého zobrazování hodnot proměnných. Pokud v některé z odvozených tříd změníme přístupové metody, bude jejich volání fungovat korektně i z metod definovaných předky.
Na výstupu z posledního příkladu stojí za zmínku ještě proměnná s počtem instancí třídy. Vytvořili jsem jednu instanci třídy Basic
a jednu instanci třídy Movable
. Zobrazovaná hodnota počtu instancí je 2. Proměnná třídy @@count
tedy zůstává společná i pro potomky třídy, ve které byla definována (což není mezi programovacími jazyky úplně obvyklé chování). Oproti tomu metoda třídy Basic.count
zůstává svázána s třídou Basic
(jak ostatně napovídá její název).
Na závěr ještě kontrolní otázka: kam nám vlastně zmizela třída Robot
? Čisté řešení v našem příkladu by pravděpodobně bylo nadefinovat ji jako potomka třídy Movable
.
Starší komentáře ke článku
Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.
Mohlo by vás také zajímat
-
Souboj na trhu s CPU pro servery: AMD vs. Intel
8. prosince 2023 -
Responzivní design: Proč by ho neměl ignorovat žádný vývojář?
27. listopadu 2023
Nejnovější
-
Jak rozšířit úložiště Macu za pětinovou cenu?
16. prosince 2024 -
Nové trendy v doménách pro osobní projekty – DIY, LIVING a LIFESTYLE
9. prosince 2024 -
Jak chránit webové stránky před Web/AI Scrapingem
27. listopadu 2024 -
Jaký monitor je nejlepší k novému Macu Mini?
25. listopadu 2024
Honza
Lis 24, 2011 v 14:01Ahoj nemelo by to byt spis „attr_accessor“ místo „acoder_accessor“?