Ruby po kapkách (11.) – definice tříd

22. května 2009

Minule jsme si ukázali základy definování vlastních tříd a předvedli jsme si, jak vytvořit přístupové metody k proměnným, které jsou jinak z vnějšku třídy nedostupné. Dnes se podíváme, jak pomocí vlastních přístupových metod oddělit vnitřní fungování třídy od služeb, které nabízí aplikace.

Předpokládejme, že se má námi vyvíjená hra prodávat zejména v USA. Až doposud se proto veškerá manipulace s hmotností odehrávala v librách. Z důvodu rozšíření fyzikálních výpočtů, které chceme implementovat ve třídě Robot, by se nám však hodilo, aby byla hmotnost uvnitř třídy reprezentována v kilogramech. Všechen kód, který třídu Robot používá však pracuje s librami. Jak tento problém jednoduše vyřešit? Stačí nám vhodně předefinovat metody pracující s proměnnou @mass.

class Robot # nová definice třídy Robot, verze 3
  LB_KG = 0.454 # konstanta pro přepočet liber na kg (libra je cca 0.454kg)
  def initialize(xpos, ypos, mass) # inicializace proměnných
    @xpos = xpos
    @ypos = ypos
    @mass = mass * LB_KG # hodnota přijde v librách, přepočítáme ji na kg
  end
  acoder_reader :xpos, :ypos # běžné přístupové metody pro čtení
  acoder_writer :xpos, :ypos # a zápis
  def mass # přístupová metoda pro čtení ‚@mass‘
    @mass / LB_KG # přepočteme zpět z kg na libry
   end
  def mass=(mass) # přístupová metoda pro zápis ‚@mass‘
    @mass = mass * LB_KG # přepočteme z liber na kg
  end
  def to_s
    „X = #{@xpos}, Y = #{@ypos}, M = #{@mass}kg“ # kontrolní výpis ponecháme v kg
  end
end

Vnitřně je hmotnost ukládána v kilogramech a vnější kód pracuje s librami. Přístupové metody provádějí potřebné konverze. Protože jsme si díky zapouzdření jisti, že k proměnným nelze přistupovat přímo, nemusíme se obávat problémů, kdybychom se v budoucnosti rozhodli převést vnitřní fungování zpět na libry.

V příkladu jsem zavedli konstantu LB_KG. Podle konvence je v Ruby název konstanty psán velkými písmeny. Tato konstanta není logicky přiřazena k instanci, ale k celé třídě, protože je pro všechny instance stejná. Jak uvidíme je přístupná dokonce i mimo třídu a prakticky tak v kombinaci se jménem třídy funguje jako globální konstanta. (Mimochodem dalo by se říci, že definováním třídy je definována konstanta stejného jména jako má třída.) Stojí za zmínku, že neměnnost konstanty v Ruby není vynucena. Interpret zobrazí při pokusu o změnu hodnoty konstanty pouze varování.

a = Robot.new(7, 8, 5) # vytvoříme novou instanci
puts a.to_s # výpis proměnných ukazuje vnitřní hodnotu v kg
puts a.mass # přístupová metoda ukazuje naopak hodnotu v librách
puts Robot::LB_KG # konverzní konstanta je pomocí ‚::‘ přístupná mimo třídu
puts Math::PI # na ukázku: konstanta vestavěného modulu obsahující pí

Po spuštění kódu získáme následující výpis:

X = 7, Y = 8, M = 2.27kg
5.0
0.454
3.141592654

Kromě konstanty sdílené všemi instancemi (a celým programem) lze zavést i proměnnou, která bude sdílena všemi instancemi. Říká se jí „class variable“ či proměnná třídy (v některých jazycích se také můžete setkat s označením statická proměnná). Název proměnné společné pro všechny instance třídy začíná v Ruby dvěma znaky ‚@@‘. Klasickým školním příkladem použití sdílené proměnné je počítání vzniklých instancí.

class Robot # definice třídy ‚Robot‘, verze 4
  LB_KG = 0.454
  @@count = 0 # class variable je nutné inicializovat v definici třídy
  def initialize(xpos, ypos, mass)
    @xpos = xpos
    @ypos = ypos
    @mass = mass * LB_KG
    @@count += 1 # při každé nové instanci přičteme k počitadlu 1
  end
  #
  # definice přístupových metod zůstává beze změny (lze ji zkopírovat
  # z minulého příkladu)
  #
  def to_s
    „X = #{@xpos}, Y = #{@ypos}, M = #{@mass}kg, C = #{@@count}“ # doplníme ‚@@count‘
  end
end

Snadno vyzkoušíme:

a = Robot.new(7, 8, 5)
b = Robot.new(1, 2, 3)
c = Robot.new(2, 3, 4)
puts a.to_s

Výsledek je podle očekávání:

X = 7, Y = 8, M = 2.27kg, C = 3

Jak nejspíše tušíte, do třetice všeho dobrého existují i metody svázané s třídou – takzvané „class methods“ neboli metody třídy (opět se můžete setkat i s označením statické metody). S jednou jsme se již setkali. Několikrát jsme použili metodu new. Metoda new nemůže být metodou objektu, protože jej teprve vytváří. Na druhé straně musí být svázána s konkrétní třídou, protože postup vytváření objektů různých tříd se může lišit.

Nadefinujme si metodu třídy Robot, která zobrazí obsah proměnné @@count.

def Robot.count # definujeme metodu s názvem ‚Třída.metoda‘
  @@count # vrací hodnotu počitadla
end

Co se týče zapouzdření není možné přímo přistupovat ani k proměnným třídy. Opět musíme využít metod. Metoda třídy má přístup k proměným třídy, logicky však nemůže přistupovat k žádným proměnným instancí, které ve chvíli volání ani nemusí existovat. Naopak metody instancí mohou přistupovat k proměnným dané instance i k proměnným třídy. Ověříme funkčnost námi definované metody.

a = Robot.new(7, 8, 5)
b = Robot.new(7, 8, 5)
c = Robot.new(7, 8, 5)
puts Robot.count # voláme v podobě ‚Třída.metoda‘

Výsledek je podle očekávání:

3

Uveďme si ještě příklady. Instance vestavěné třídy File reprezentuje otevřený soubor. Otevřený soubor však nelze korektně smazat. Mazání tedy nemůže být realizováno metodou instance. Proto existuje metoda třídy File.delete, která smaže soubor, jehož název dostane jako parametr. Stejně tak metoda File.open otvírá soubor a teprve vytváří instanci třídy. Je to vlastně synonymum pro metodu File.new. A metoda new, jak jsme již uvedli, je typickou ukázkou metody svázané se třídou.

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

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

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *