Jak jsme se dozvěděli již v úvodu série o OOP v Ruby, platí, že proměnné definované v rámci třídy jsou pro okolní program skryté a je možné k nim přistupovat jen pomocí příslušných metod. Možná vás napadlo, zda je možné omezit také přístup k některým metodám. Ve skutečnosti jsme se s jednou metodou, k níž nelze přistupovat mimo definici třídy již setkali.

a = Basic.new(1, 2, 3)
a.initialize(4, 5, 6)

Spuštění tohoto fragmentu kódu skončí chybou.

private method ‚initialize‘ called for #<Basic:0xa03bcc0>(NameError)

Metoda initialize je totiž v Ruby označena jako private a pokus o její volání končí vznikem výjimky. Celkem mohou být metody v Ruby z hlediska řízení přístupu zařazeny do tří skupin:

  • public
  • protected
  • private

(Hned na tomto místě upozorněme, že význam označení protected je v Ruby mírně odlišný od jazyků C++ nebo Java.)

Není-li stanoveno jinak, jsou nově definované metody automaticky označeny jako public. To umožňuje jejich volání jinými metodami téže třídy i jakýmikoliv kódem mimo třídu. Metoda initialize je automaticky označena jako private. Tyto metody lze volat pouze v rámci definice třídy, ve které jsou definovány. Jinými slovy můžeme říci, že je lze volat pouze bez určení objektu – příjemce (tj. není možné použít volání ve formě objekt.metoda a tudíž je volání možné vlastně jen v rámci jedné instance).

Metody označené jako protected tvoří střední cestu. Lze je volat v rámci definice stejné třídy. Navíc mohou být volány s určením objektu, ten musí však opět patřit do stejné třídy. Tyto metody tesy mohou být volány navzájem mezi instancemi jedné třídy.

K přepínání přístupu k metodám existují v Ruby dva jednoduché způsoby:

class Dummy
  # defaultně jsou metody public
  #
  private
  # metody definované tady jsou private
  #
  protected
  # metody definované tady jsou protected
  #
  public
  # metody definované tady jsou opět public
  #
  # alternativně můžeme přístup k metodám nastavit hromadně
   public :method1, :method4
  protected :method2
  private :method3
end

Vraťme se na chvíli k našemu příkladu s robotickou hrou. Čas běží a investoři tlačí na urychlení vývoje. Rozhodli jsme se proto zdokonalit definici třídy Movable. Nebudeme nyní nastavovat hodnoty směru a rychlosti přímo, ale vytvoříme metody pro zatáčení a akceleraci. Přímé nastavení hodnot by již nemělo být možné. Dále připravíme triviální metodu, která bude simulovat srážku objektu s jiným objektem. Prozatím se spokojíme s tím, že kolize náhodně změní směr a rychlost obou dotčených objektů. Budeme rozšiřovat poslední verzi definice třídy Movable.

class Movable # při doplnění definice nemusíme opakovat určení předka
  protected :direction=, :velocity= # nastavování hodnot bude protected
  def steer(s) # metoda pro změnu směru objektu
    @direction += s # směr se změní o zadaný parametr
  end
  def accelerate(a) # metoda pro změnu rychlosti objektu
    @velocity += a # rychlost se změní o zadaný parametr
  end
  def crash(o) # simulace srážky s jiným objektem
    direction = rand(direction) # vlastní rychlost a směr se náhodně
    velocity = rand(velocity) # změní
    o.direction = rand(o.direction) # a změní se i rychlost a směr objektu
    o.velocity = rand(o.velocity) # zadaného jako parametr
  end
end

Doplněnou definici ihned vyzkoušíme.

a = Movable.new(3, 3, 10, 10, 5)
puts a.to_s
b=Movable.new(3, 3, 10, 7, 13)
puts b.to_s
a.crash(b) # objekt ‚a‘ dostal zprávu, aby narazil do objektu ‚b‘
puts a.to_s
puts b.to_s
a.steer(3) # objekt ‚a‘ zatáčí
puts a.to_s
b.direction = 7 # objekt ‚b‘ se pokouší o přímé nastavení směru

Získáme výpis podobný tomuto:

Movable: X = 3, Y = 3, M = 10kg, C = 1, D = 10, V = 5
Movable: X = 3, Y= 3, M = 10kg, C = 2, D = 7, V = 13
Movable: X = 3, Y= 3, M = 10kg, C = 2, D = 10, V = 5
Movable: X = 3, Y= 3, M = 10kg, C = 2, D = 6, V = 1
Movable: X = 3, Y= 3, M = 10kg, C = 2, D = 13, V = 5
protected method ‚direction=‘ called for #<Movable:0xa03bca8>(NameError)

Zatímco objekty mohou navzájem volat své metody označené jako protected, volání z kódu mimo objekt skončilo výjimkou.

Řízení přístupu k metodám představuje po izolaci proměnných další podobu objektového principu zapouzdření. Na poněkud obecnější úrovni lze říct, že velká část úspěchu OOP tkví v poskytnutí prostředků pro organizaci zdrojového kódu do ucelených bloků. Hotové části kódu s dobře dokumentovaným rozhraním lze použít i v budoucích aplikacích a značně se tak zrychluje vývoj.

Třída je v OOP základní konstrukcí pro logické strukturování programů. Na druhé straně stojí fyzické rozdělení kódu do více souborů. K němu v Ruby slouží obvyklý mechanismus vložení souboru realizovaný voláním require.

require ‚date‘ # vložíme soubor ‚date.rb‘, kde je definována třída ‚Date‘
puts Date.today # vyzkoušíme třídu ‚Date‘

Výsledkem je dnešní datum. Součástí instalace Ruby je knihovna standardních tříd, které jsou uloženy ve větším počtu menších souborů. Definici potřebných tříd můžeme do programu vkládat voláním require. Metoda require nahradí svůj výskyt v kódu obsahem souboru, který jí předáme jako parametr.

Pro hledání souboru existují relativně složitá pravidla. Defaultně se hledá v adresářích, kde jsou uloženy součásti standardní knihovny a v aktuálním adresáři. Pokud není soubor nalezen, pokouší se interpret ještě přidat k názvu koncovku .rb nebo koncovky dynamických knihoven na dané platformě (například .so nebo .dll). Tímto způsobem se načítají knihovny napsané v jiných jazycích a zkompilované do nativního kódu.

Při běhu programu máme k dispozici dvě globální proměnné, které nám poskytují informace o prohledávaných adresářích a o aktuálně načtených souborech pomocí require.

require ‚date‘ # vložíme soubor
puts $: # proměnná, která obsahuje cesty prohledávané metodou require
puts
puts $“ # proměnná, která obsahuje názvy načtených souborů

Zobrazí se několik řádků, které mohou vypadat například takto.

/usr/local/lib/ruby/site_ruby/1.8
/usr/local/lib/ruby/site_ruby/1.8/i386-freebsd7
/usr/local/lib/ruby/site_ruby
/usr/local/lib/ruby/1.8
/usr/local/lib/ruby/1.8/i386-freebsd7
.
date.rb

Volání metody require se může v kódu programu objevit prakticky kdekoliv, kde je možné volat metodu. Možné je i vícenásobné vložení stejného souboru (například do několika různých definic tříd).

Někde mezi třídou a souborem se nachází ještě jedna úroveň organizování kódu, kterou lze v Ruby použít. Jsou jí moduly. Modul je ohraničená část kódu velmi podobná definici třídy. Nejlépe si problematiku objasníme na příkladu. Vraťme se proto opět k vyvíjené hře. Za účelem urychlení prací byl najat další tým, který se zabývá programovaním matematických a fyzikálních funkcí, které budeme v simulaci potřebovat. Prvním výsledkem je modul Matphys, který nabízí metodu pro výpočet vzdálenosti dvou bodů na ploše.

module Matphys # začátek definice modulu
  def Matphys.distance(x1, y1, x2, y2)
    Math.sqrt((x2 – x1) * (x2 – x1) + (y2 – y1) * (y2 – y1)) # 2D vzdálenost
  end
end # konec definice modulu
puts Matphys.distance(3, 4, 5, 6) # ověříme funkci metody z modulu

Výsledkem testovacího výpisu, který následuje definici modulu je:

2.828427125

Metoda Matphys.distance je definována včetně názvu modulu a musíme ji také tak volat. Jedná se o analogii s metodami tříd. Při pozornějším pohledu na metodu Matphys.distance uvidíte volání Math.sqrt, což je metoda pro výpočet odmocniny. Pokud hádáte, že Math je modul, hádáte správně. Kromě tříd je část standardní knihovny v Ruby organizována do modulů.

Stejně jako definice třídy, ani definice modulu není uzavřena a můžeme ji kdykoliv doplnit:

module Matphys
  G = 9.81 # definice konstanty
end
puts Matphys::G # testovací výpis konstanty z modulu

Nadefinovali jsme v modulu konstantu. Výsledkem výpisu je samozřejmě:

9.81

V modulech je možné používat i proměnné. To má však smysl spíše v případě, kdy Ruby využívá moduly jako takzvané mixiny. O tom však zase až v příštím díle.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět