FIGYELEM! Ez a dokumentum kizárólag az ELTE IK hallgatók számára oktatási célra készült! Félkész munka, dolgozunk rajta! Terjeszteni, felhasználni máshol a szerzők engedélye nélkül tilos!

Még néhány szó metódusokról


Más nyelvek rendelkeznek függvényekkel, eljárásokkal, metódusokkal, vagy rutinokkal. A Ruby-ban csak metódusok vannak - egy kódrészlet, mely visszatérési értékkel rendelkezik.

Ebben a könyvben ezidáig különösebb meggondolás nélük definiáltunk, és használtunk különféle metódusokat. Itt az ideje, hogy picit elmélyedjünk a dolog részleteiben.

Egy metódus definiálása

Eddigi tapasztalatok alapján egy metódust a def kulcsszóval definiáltunk, melynek neve kisbetűvel kezdődik. (Nem kapunk azonnali hibaüzenetet nagybetűs kezdés esetén, de az üzenet hívásakor a Ruby először egy konstanst fog keresni, nem pedig metódust! Ez pedig már némi félreértést okozhat...) A lekérdezés jellegű metódusok (például az instance_of?) gyakran kérdőjellel (?) végződnek. A veszélyes metódusok (azok, melyek módosítják a fogadót) nevei általában felkiáltójellel (!) végződnek. Például a String osztály chop, és chop! metódussal is rendelkezik. Az első egy módosított string-et ad vissza, a második az adott objektumot módosítja. Megjegyeznénk azonban, hogy a ? és a ! az egyedüli furcsa karakterek, amelyeket metódusok neveiben használhatunk.

Miután megadtuk az új metódusunk nevét, esetleg meg kell határoznunk néhány paramétert. Ezek nemes egyszerűséggel lokális változók egy felsorolása, vesszővel (,) elválasztva. Néhány példadeklaráció:

def azUjMetodusom(arg1, arg2, arg3)  # 3 argumentum
  # A metódus kódja itt következik
end
def egyMasikUjMetodusom      # Nincs argumentum
  # A metódus kódja itt következik
end

A Ruby - más nyelvekhez hasonlóan - lehetőséget ad ezen argumentumok alapértelmezett értékének meghatározására (azaz olyan értékek megadására, amelyek lépnek érvénybe, amikor a metódus hívója explicit nem adott nekik értéket). Ezt az értékadás operátorral tehetjük meg.

def coolDude(arg1="Miles", arg2="Coltrane", arg3="Roach")
  "#{arg1}, #{arg2}, #{arg3}."
end
coolDude
»  "Miles, Coltrane, Roach."
coolDude("Bart")
»  "Bart, Coltrane, Roach."
coolDude("Bart", "Elwood")
»  "Bart, Elwood, Roach."
coolDude("Bart", "Elwood", "Linus")
»  "Bart, Elwood, Linus."

A metódus törzse megszokott Ruby kifejezéseket tartalmaz, kivéve, hogy nem definiálhatunk példánymetódust, osztályt, vagy modult egy metóduson belül. A visszatérési érték vagy az utolsó lefuttatott kifejezés éstéke, vagy egy explicit return kifejezés értéke.

Változó számű argumentumok

De mi van, ha egy metódusnak előre nem meghatározott számú argumentumot akarunk átadni, vagy több paramétert akarunk egy változóba összefogni? Egy - a paraméter neve előtt elhelyezett - csillag (*) karakterrel ezt is megtehetjük.

def varargs(arg1, *rest)
  "Got #{arg1} and #{rest.join(', ')}"
end
varargs("one")
»  "Got one and "
varargs("one", "two")
»  "Got one and two"
varargs "one", "two", "three"
»  "Got one and two, three"

Ebben a példában az első argumentum a metódus első paraméteréhez rendelődik hozzá, mint általában. Ám a következő paraméter egy *-gal kezdődik, tehát minden további argumentum egy új Array objektumban tárolva kerülnek bele az adott paraméterbe.

Metódusok és blokkok

Az iterátorokról és blokkokról szóló fejezetünkben tárgyaltakhoz hasonlóan egy metódus hívásakor hozzákapcsolódhat egy blokkhoz. Normális esetben egyszerűen a yield-del hívjuk a blokkot a metódus belsejéből.

def takeBlock(p1)
  if block_given?
    yield(p1)
  else
    p1
  end
end
takeBlock("no block")
»  "no block"
takeBlock("no block") { |s| s.sub(/no /, '') }
»  "block"

Emellett, ha a metódusdefinícióban szereplő utolsó paraméter & jellel kezdődik, bármely kapcsolódó blokk egy Proc objektummá konvertálódik, majd ezt az objektumot kapja a megfelelő paraméter.

class TaxCalculator
  def initialize(name, &block)
    @name, @block = name, block
  end
  def getTax(amount)
    "#@name on #{amount} = #{ @block.call(amount) }"
  end
end
tc = TaxCalculator.new("Sales tax") { |amt| amt * 0.075 }
tc.getTax(100)
»  "Sales tax on 100 = 7.5"
tc.getTax(250)
»  "Sales tax on 250 = 18.75"

Egy metódus hívása

Egy metódust a fogadó, a metódus neve, a paraméterek, valamint az esetlegesen kapcsolódó blokk megadásával hívhatunk.

connection.downloadMP3("jitterbug") { |p| showProgress(p) }

Ebben a példában a connection objektun a fogadó, a downloadMP3 a metódus neve, jitterburg a paraméter, valamint {...} rész a kapcsolódó blokk.

Az osztályok-, és modulok metódusai esetén a fogadó az osztály, vagy modul neve.

File.size("testfile")
Math.sin(Math::PI/4)

Ha elhagyjuk a fogadót, az alapértelmezésként a self értéket (önmagát) kapja.

self.id
»  537794160
id
»  537794160
self.type
»  Object
type
»  Object

Ez az alapértelmezett mechanizmus az, ahogy a Ruby a privát metódusokat implementálja. A privát (private) metódusok nem hívhatók fogadóval, tehát azok csak az adott objektumban elérhető metódusoknak kell lenniük.

Az opcionális paraméterek a metódus nevét követik. Ha nincs félreérthetőség, egy metódus hívásakor az argumentumlista körüli zárójeleket elhagyhatjuk. Habár, a legegyszerűbb esetektől eltekintve, ezt nem ajánljuk. Vannak olyan nehezen megfogható hibák, amin elcsúszhatunk. A mi szabályunk: ha kicsit is kétséges, használjunk zárójeleket.

a = obj.hash
# Ugyanaz, mint
a = obj.hash()
# ez.
obj.someMethod "Arg1", arg2, arg3
# Ugyanaz, mint
obj.someMethod("Arg1", arg2, arg3)
# zárójelekkel.

Tömbök kiterjesztése metódushívásokban

Korábban láttuk, ha egy metódus definíciójában egy formális paraméter elé * karaktert teszünk, a metódus hívásakor megadott paraméterek egy tömbbe lesznek csomagolva. Nos, ugyanez visszafelé is működik.

Amikor egy metódust hívunk, kibonthatunk egy tömböt, így minden eleme egy-egy elkülönített paraméterként fog átadódni. Ezt pedig az argumentum tömb elé illesztett * karakterrel tehetjük.

def five(a, b, c, d, e)
  "I was passed #{a} #{b} #{c} #{d} #{e}"
end
five(1, 2, 3, 4, 5 )
»  "I was passed 1 2 3 4 5"
five(1, 2, 3, *['a', 'b'])
»  "I was passed 1 2 3 a b"
five(*(10..14).to_a)
»  "I was passed 10 11 12 13 14"

Blokkok dinamikusabbá tétele

Láttuk, hogy egy blokkot összeköthetünk egy metódushívással.

listBones("aardvark") do |aBone|
  # ...
end

Általában ez tökéletesen elég - egy fix blokkot egy metódushoz kötünk, ugyanúgy, mint egy kódrészletet az if, vagy a while utasítás után.

Néha azonban szeretnénk rugalmasabbak lenni. Például, lehet hogy matematikát tanítunk. A diákunk kér egy n-plussz, és egy n-szer táblázatot. Ha a diákunk egy 2-szer táblázatot kér, mi azt mondjuk: 2,4,6,8,... (Ez a kód nem tartalmaz hibakezelést.)

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i
if times =~ /^t/
  puts((1..10).collect { |n| n*number }.join(", "))
else
  puts((1..10).collect { |n| n+number }.join(", "))
end

Eredménye:

(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Ez működik, de elég csúnya, ráadásul az if mindkét ágában majdnem azonos utasítások szerepelnek. Szép lenne, ha ki tudnánk emelni a számítást végző blokkot.

print "(t)imes or (p)lus: "
times = gets
print "number: "
number = gets.to_i
if times =~ /^t/
  calc = proc { |n| n*number }
else
  calc = proc { |n| n+number }
end
puts((1..10).collect(&calc).join(", "))

Eredménye:

(t)imes or (p)lus: t
number: 2
2, 4, 6, 8, 10, 12, 14, 16, 18, 20

Ha egy metódus utolsó argumentuma egy & jellel kezdődik, a Ruby feltételezi, hogy az egy Proc objektum, eltávolítja a paraméterlistából, blokká alakítja, és hozzáköti a metódushoz.

Ezzel a technikával kicsit megfűszerezhetjük a blokkok használatát. Például, néha szeretnénk egy iterátort, és egy tömbben tárolni minden értéket, amit felvesz. Újra felhasználjuk a Fibonacci számokat generáló kódunkat.

a = []
fibUpTo(20) { |val| a << val }
»  nil
a.inspect
»  "[1, 1, 2, 3, 5, 8, 13]"

Ez is működik, de a szándékunk annyira érthető, mint szeretnénk. Ehelyett definiálunk egy into metódust, amely visszaadja a tömböt feltöltő blokkot. (Vegyük észre, hogy ugyanakkor a visszaadott blokk egy záradék - az anArray paraméterre hivatkozik az into metódus visszatérése után is.)

def into(anArray)
  return proc { |val| anArray << val }
end
fibUpTo 20, &into(a = [])
a.inspect
»   "[1, 1, 2, 3, 5, 8, 13]"

Hasító argumentumok kigyűjtése

Néhány nyelv támogatják a kulcsszó argumentumokat, azaz az argumentumok adott sorrendben és mennyiségben történő megadása helyett az argumentumok nevét és értékét adjuk meg, tetszöleges sorrendben. A Ruby 1.6-ban ez a funkció még nem szerepel, habár az 1.8-asba má be van ütemezve.

Ezalatt az emberek a hasítókat használják ugyanezen hatás elérésére. Például, szeretnénk egy még erősebb név-alapú keresést hozzáadni a SongList osztályunkhoz.

class SongList
  def createSearch(name, params)
    # ...
  end
end
aList.createSearch("short jazz songs", {
       'genre'            => "jazz",
       'durationLessThan' => 270
       } )

Az első paraméter a keresés neve, a második pedig egy hasító, benne a keresési paraméterekkel. A hasító használatával szimulálhatjuk a kulcsszavakat: olyan számokat keresünk, amelyek jazz stílusúak, és kevesebb, mint 4 és fél perc hosszúak. Habár ez a megközelítés kicsit kényes, a zárójelezés miatt könnyen összetéveszthető egy, a metódussal összekötött blokkal. Elhelyezhetünk kulcs => érték párokat egy argumentumlistában mindaddig, amíg valamennyi normális paramétert követnek, és megelőznek minden tömb, vagy blokk argumentumot. Ezek a paraméterek aztán egy hasítóba kerülnek, és egy paraméterként adódnak át a metódusnak. Így nincs szükség kapcsos zárójelekre.

aList.createSearch("short jazz songs",
       'genre'            => "jazz",
       'durationLessThan' => 270
       )

< Előző oldalKövetkező oldal >