Ruby po kapkách (14.) – mixiny
Podíváme se nyní na další možnosti použití modulů v Ruby. Připoměňme si závěr minulého dílu. Zkusíme poněkud přepsat náš modul Matphys
. Metoda distance
bude nyní definována analogicky jako metoda instance třídy (jinými slovy odstraníme název modulu z definice metody).
module Matphys
def distance(x1, y1, x2, y2)
Math.sqrt((x2 – x1) * (x2 – x1) + (y2 – y1) * (y2 – y1))
end
end
include Matphys # abychom mohli volat metodu modulu
puts distance(3, 4, 5, 6) # kontrolní výpis
Dopracovali jsem se ke stejnému výsledku jako v předešlém případě. Díky metodě include
máme nyní k dispozici všechny definice z modulu Matphys
. Zatímco require
pracuje se soubory, include
pracuje s moduly (je vhodné si uvědomit odlišnost od jazyků jako C, Java nebo PHP). Modul může být uložen ve stejném souboru, kde je použit pomocí include
. Pokud je uložen v jiném souboru, je třeba tento soubor nejdříve vložit voláním require
.
Připoměňme si, že pokud není řečeno jinak (není uvedena cílová instance), volají se metody třídy Object
(například puts
). Mohli bychom proto také napsat:
module Matphys
def distance(x1, y1, x2, y2)
Math.sqrt((x2 – x1) * (x2 – x1) + (y2 – y1) * (y2 – y1))
end
end
class Object
include Matphys # modul se použije v rámci definice třídy
end
puts distance(3, 4, 5, 6) # voláme metodu, která přibyla do třídy ‚Object‘
Výsledek výpisu je identický. Vložení modulu do definice třídy se v terminologii Ruby nazývá doslova namixování. Vložený modul se pak označuje jako mixin. Zdánlivě jednoduchý mechanismus nabízí nečekané možnosti. Několikrát jsme již zmiňovali vícenásobnou dědičnost. Ta vzniká, pokud třída má více než jednoho přímého předka. Vícenásobné dědění často odpovídá modelované skutečnosti. Třída „pes“ je logicky potomkem třídy „savec“, ale může být zároveň potomkem třídy čtvernožec“, která může být současně předkem třídy „stůl“.
Na druhou stranu je s vícenásobnou dědičností spojena řada problémů. Například je třeba definovat pravidla, pro případ, kdy více předků definuje stejnou metodu nebo určit jakým způsobem volat konstruktory předků.
Ruby proto nepodporuje vícenásobnou dědičnost jako takovou (třída nemůže mít více předků), ale pomocí mixinů nabízí prakticky všechny výhody, které vícenásobné dědění poskytuje.
Pokračujme v našem příkladu a zakomponujme modul Mathpys
do třídy Movable
.
class Movable # doplníme definici třídy
include Matphys # namixujeme modul
def distance_to(o)
distance(xpos, ypos, o.xpos, o.ypos) # vzdálenost k jinému objektu
end
end
Metodu distance
definovanou modulem Matphys
jsme ihned využili pro definici nové metody distance_to
, která zjistí vzdálenost objektu, ke kterému náleží, od objektu, který má zadaný jako parametr. Její fungování ověříme následovně.
a = Movable.new(10, 10, 5, 0, 0)
b = Movable.new(20, 20, 5, 0, 0)
puts a.to_s
puts b.to_s
puts a.distance_to(b)
Získáme výpis:
Movable: X = 10, Y = 10, M = 5kg, C = 4, D = 0, V = 0
Movable: X = 20, Y = 20, M = 5kg, C = 4, D = 0, V = 0
14.14213562
Kýženého výsledku bychom mohli dosáhnout i mírně odlišnou cestou.
module Matphys
def distance(a, b)
Math.sqrt((a.xpos – b.xpos) * (a.xpos – b.xpos) + (a.ypos – b.ypos) * (a.ypos – b.ypos))
end
end
class Movable
include Matphys
def distance_to(o)
distance(self, o)
end
end
Výše uvedený test proběhne beze změny s očekávaným výstupem. Provedená úprava směřovala k větší interakci mezi metodami definovanými v modulu a metodami definovanými ve třídě. Modul nyní používá volání přístupových metod xpos
a ypos
. Je to k něčemu dobré? Kromě ukázky faktu, že v Ruby je obvykle možné dělat věci více různými způsoby, nám příklad umožní lépe porozumět elegantnímu využití mixování modulů ze standardní knihovny.
Ve standardní knihovně existuje modul Comparable
, který definuje rozličné relační operátory (připomeňme, že operátory jsou definovány jako metody). Jedinou podmínkou je, že předem musí být definovaný základní relační operátor <=>
, který vrací:
- -1, pokud je levý operand menší než pravý,
- 0, pokud jsou si operandy rovny a
- 1, když je pravý operand větší než levý.
Řekněme, že pro objekty třídy Movable
platí, že „větší“ při porovnání je ten, který ma vyšší rychlost. Můžeme proto nadefinovat operátor jako:
class Movable
def <=>(o)
self.velocity <=> o.velocity
end
end
Pravděpodobně již tušíte, že celou plejádu dalších relačních operátorů získáme tímto krátkým kódem:
class Movable
include Comparable
end
Můžeme ověřit, zda jsou skutečně koláče bez práce:
a = Movable.new(0, 0, 10, 0, 5)
b = Movable.new(0, 0, 10, 0, 6)
puts b > a
Dozvíme se, že:
true
Podobným způsobem jako Comparable
funguje i modul Enumerable
, se kterým jsem se již setkali. Připomeňme, že je určen pro třídy fungující jako kontejnery (uchovávají reference na jiné objekty – například pole nebo hash). Vytvoříme-li si vlastní kontejnerovou třídu, postačí nám nadefinovat metodu each
, která spouští zadaný blok pro každý prvek kontejneru. Pak namixujeme modul Enumerable
a získáme například metody pro prohledávání prvků. Pokud máme zároveň definovaný i operátor <=>
, můžeme použít metodu sort
pro získání setříděného pole prvků našeho kontejneru.
Na závěr našeho úvodu do OOP v Ruby se pojďme ještě podívat na vlastnost, kterou nabízejí některé objektové jazyky. Jedná se o takzvané přetěžování metod. To je – jednoduše řečeno – možnost definovat několik metod se stejným názvem, které se liší počtem a typem parametrů. U jazyka s dynamickým typováním (jakým je i Ruby) je koncept přetěžování nesmyslný. Podobného efektu lze dosáhnout zkoumáním aktuálního typu parametru za běhu programu a využitím defaultních hodnot parametrů.
class Movable
def steer(s = 1, d = 1) # přiřadíme defaultní hodnoty parametrům
if d.kind_of? String # test – je parametr řetězec?
d =- 1 if d == ‚left‘ # převedeme řetězec ‚left‘ na -1
d = 1 if d == ‚right‘ # a řetězec ‚right‘ na +1
end
@direction += s * d # zatočíme ve správném směru
end
end
a=Movable.new(10, 10, 5)
puts a.to_s
a.steer # oběma parametrům bude přiřazena defaultní hodnota
puts a.to_s
a.steer(5) # první parametr je zadaný, druhý bude default
puts a.to_s
a.steer(3, -1) # druhý parametr je číslo
puts a.to_s
a.steer(4, ‚right‘) # druhý parametr je řetězec
puts a.to_s
Po spuštění bychom měli vidět výpis podobný tomuto:
Movable: X=10, Y=10, M=5kg, C=1, D=0, V=0
Movable: X=10, Y=10, M=5kg, C=1, D=1, V=0
Movable: X=10, Y=10, M=5kg, C=1, D=6, V=0
Movable: X=10, Y=10, M=5kg, C=1, D=3, V=0
Movable: X=10, Y=10, M=5kg, C=1, D=7, V=0
Pomocí defaultních hodnot parametrů jsme dosáhli možnosti volání metody steer
s žádným až dvěma parametry. Druhý parametr, který určuje směr zatáčení, může být navíc buď číslo (-1 – doleva, 1 doprava) nebo řetězec. O aktuálním typu parametru rozhodujeme pomocí metody kind_of?
. (V uvedené definici metody steer
je záměrně ponechána potenciální chyba, jejíž odstranění se ponechává jako cvičení pro laskavého čtenáře.)
Obecně proměnlivý počet parametrů lze zpracovat také následující syntaktickou konstrukcí.
def vararg(*s) # ‚s‘ bude vždy pole
puts s.size # velikost pole – počet parametrů
end
vararg # otestujeme s různými počty a typy parametrů
vararg(‚a‘)
vararg(‚a‘, 1, 2, ‚b‘)
Poslednímu parametru metody může být předřazena hvězdička. Tento parametr je pak považován za pole a případné přebytečné parametry jsou uloženy jako jeho prvky. Na výpisu vidíme:
0
1
4
A to je pro dnešek vše.
Mohlo by vás také zajímat
-
Vstupte do éry umělé inteligence: ASOME Max Studio s AMD Ryzen™ 9 7940HS
14. listopadu 2023 -
Nové AI modely od Open AI a Google
22. května 2024 -
Nepodceňte UX na vašem webu: Proč na něm záleží?
10. dubna 2024
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