1.4.1.1. fejezet, Stringek, számok, osztályok és objektumok

A Ruby-ban programozás egyszerű. Nem típusos, kis/nagybetű megkülönböztető (myvar és myVar között van különbség), gets az egyszerű szöveg bevitelre, print, puts (sortörést tesz a string végére) az egyszerű kiírásra való.

print( 'Enter your name: ' ) 
name = gets() 
puts( "Hello #{name}" )

Nincs változó deklaráció, vagy tipus megadás. A gets után a zárójel kirakása nem kötelező, habár megkönnyíti a különbségtételt változó és metódushívás között.

Stringek és beágyazott kiértékelés

Az utolsó parancsnál puts( "Hello #{name}" ) a name változó beágyazódik, értékét a stringbe írja ki. Ez a #{} kifejezés a macskaköröm ("") jelek között kiértékeli a benne megadott kifejezést, ami lehet összetett is. Pl.:

puts( "\n\t#{(1 + 2) * 3}\nGoodbye" )

A szimpla idézőjelek ('') közé írva nincs ilyen kiértékelés. A macskakörmök közt a \n és \t is működik, sortörést és tabulátorjelet tesz a stringbe.

Számok

Az alábbiakban áfát számolunk, és megismerkedünk a to_f string metódussal

#!/usr/bin/ruby
# Ez egy megjegyzes
taxrate = 0.175
print "Enter price (ex tax): " 
s = gets 
subtotal = s.to_f
tax = subtotal * taxrate # Ez is egy megjegyzés.
puts "Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}"

A string objektum metódusa a to_f, amivel törtszámmá alakítunk egy stringet. Ha nem megy az átváltás, 0.0-t kapunk. pl.: “Hello world”.to_f értékét. A program elejére egy megjegyzés került az értelmező elérhetőségével, majd a következő sorba egy egyszerű kommentár. A # jel után bárhova egy sornyi megjegyzés írható. Az alábbi pedig egy többsoros megjegyzés:

=begin 
This is a 
multiline 
comment 
=end

Kifejezés kiértékelése, if..then

taxrate = 0.175
print "Enter price (ex tax): "
s = gets
subtotal = s.to_f
 
if (subtotal < 0.0) then
  subtotal = 0.0
end
 
tax = subtotal * taxrate
puts "Tax on $#{subtotal} is $#{tax}, so grand total is $#{subtotal+tax}"

Ha ez if feltételben megadott kifejezés igaz, lefut a következmény blokk (subtotal = 0.0). A then kulcsszót akkor kötelező kiírni, ha egy sorban van a feltétel a következmény blokkal. Tehát:

if (subtotal < 0.0) then subtotal = 0.0 end

Ezzel egy-két sorral rövidebb a kód, de az áttekinthetősége rovására. Ugyanis ha a következmény részben több kifejezést zsúfolunk, pontosvesszővel (;) kell elválasztanunk azokat egymástól. Az if után az end viszont nem opcionális.

Lokális és globális változók

A subtotal, tax és taxrate változók, kisbetűvel kezdődnek, ezért lokális változók (main), $ jelöli a globális változókat. Pl.:

localvar = "hello"
$globalvar = "goodbye" 
 
def amethod
  localvar = 10
  puts( localvar )
  puts( $globalvar )
end
 
def anotherMethod
  localvar = 500
  $globalvar = "bonjour"
  puts( localvar )
  puts( $globalvar )
end
 
 
amethod
puts
anotherMethod
puts
amethod
puts
puts( localvar )
puts( $globalvar )

Eredménye:

10
goodbye
 
500
bonjour

10
bonjour
 
hello
bonjour

Osztályok és objektumok

Most ahelyett, hogy belemennénk a részletekbe a procedurális nyelveknél megszokott kifejezések értelmezésében, úgy mint az típusok, iteráció, modulok, gyorsan tegyünk egy pillantást az osztályok és objektumok világába. Az osztály a lenyomata egy objektumnak. Definiálja a tulajdonságait, adat tagjait, a metódusain keresztül a viselkedését. Az osztályból készíthetők aztán a példányok. Így egy Macska osztályból lehet több cica objektum, mint mirmur, kandurbandi és micike. A metódus olyan mint egy függvény vagy eljárás, csak az osztályban definiáljuk, és a példányok tulajdonságain lehet dolgozni ezekkel.

Az olyan OOP nyelvek igen elterjedtek, mint a C++, C#, Object Pascal, Java, stb. A Ruby megszállottan objektum orientált. Amíg nem programoztál Smalltalk vagy Eiffel nyelveken, amik még inkább azok. Még az operátorok, mint a + és - műveletek is azok.

x = 1 + 2

Itt a + jel metódusa a Fixnum objektumnak (Integer), és az = jel itt a kivétel, amin nem egy metódus, hanem egy beépített izé (ez nem a legjobb meghatározása, nem meghatározott valami).

Most nézzük, hogyan definiálja a Ruby az osztályokat. Készítsünk egy kutya osztályt

class Dog
  def set_name( aName ) 
    @myname = aName
  end
end

Itt észrevehető, hogy az osztály definíció a class kulcsszóval kezdődik (kisbetűvel). Az osztálynév nagybetűvel kezdődik, és egy set_name metódusnév is van a def kulcsszó mögött. Zárójelbe mögötte a metódus argumentuma, majd a metódus blokk, amiben egy @ jel után az objektum myname tulajdonságának adunk értéket

Példány változók, avagy objektum tulajdonságok

Nem szükséges elő-deklarálni az objektum tulajdonságokat. Lehet egy metóduson belül is az új változó, mint az előző set_name metódusban. Példányosítani az osztályt a new metódussal lehet. Most készítsünk két kutyát, és ne feledjük, hogy az osztály nagybetűs, a példány változó kisbetűs.

mydog = Dog.new
yourdog = Dog.new

Ebben a pillanatban a két kutyának nincsen neve. Szóval a következő lépésként nevezzük el őket.

mydog.set_name( 'Fido' )
yourdog.set_name( 'Bonzo' )

Az egységbe zárás elve szerint a belső tulajdonságokat nem lehet kívülről elérni, módosítani vagy lekérdezni, csak kívülről hívható metóduson keresztül. Ezért írjunk a kutya osztályhoz egy lekérdező metódust is.

def get_name 
  return @myname
end

A return kulcsszó itt nem szükségszerű. Ha elhagyjuk, a Ruby a blokkok végén az utoljára kiadott kifejezéssel dolgozik. Most, hogy van kutya osztályunk, adjunk hozzá még egy metódust, a talk formájában. Az így létrehozott osztályunk a következő:

class Dog   
  def set_name( aName )
    @myname = aName
  end
 
  def get_name
    return @myname
  end
 
  def talk
    return 'woof!'
  end
end

Mostmár a létrehozott kutyánknak tudunk nevet adni, és szóra bírhatjuk.

mydog = Dog.new 
mydog.set_name( 'Fido' ) 
puts(mydog.get_name)
puts(mydog.talk)

Nos, van egy kutya és egy macska osztályunk. Annyi különbséggel, hogy a kutya azt mondja woof!, a macska pedig hogy miaow.

class Cat  
  def set_name( aName )
    @myname = aName
  end
 
  def get_name
    return @myname
  end
 
  def talk
    return 'miaow!'
  end
end

Készítsünk néhány példányt belőlük

# --- Create instances (objects) of the Dog and Cat classes
mydog = Dog.new
yourdog = Dog.new
mycat = Cat.new
yourcat = Cat.new
someotherdog = Dog.new
 
# --- Name them
mydog.set_name( 'Fido' )
yourdog.set_name( 'Bonzo' )
mycat.set_name( 'Tiddles' )
yourcat.set_name( 'Flossy' )
 
 
# --- Get their names and display them
# Dogs
puts(mydog.get_name)
puts(yourdog.get_name)
# hmmm, but what happens here if the dog has no name?
puts(someotherdog.get_name)
# Cats
puts(mycat.get_name)
puts(yourcat.get_name)
 
# --- Ask them to talk
puts(mydog.talk)
puts(yourdog.talk)
puts(mycat.talk)
puts(yourcat.talk)

Hoppá, úgy tűnik a someotherdog kutyának nem adtunk nevet. Hogy ne forduljon ilyen elő többet, erre nézünk később megoldást.

Üzenetek, metódusok és többalakúság (polimorfizmus)

A mintaprogram egyébként hasonlít egy klasszikus Smalltalk demóra, ami megmutatja hogy az azonos üzenetre (talk) hogyan válaszolhatnak különbözőképp az objektumok (mint pl. a macska és a kutya). Ezt nevezik polimorfizmusnak az OOP nyelvek.
Apropó! Kódrészek a Ruby-ban nem csak úgy léteznek, vagy futnak. Van egy main objektum, ami egységbe zárja a kódrészeket, és ez az objektum az első és utolsó, ami létrejön és felszámolódik a programrészek futása során. Próbáljuk csak ki a következőket:

puts self 
puts self.class

Eredmény:

main
Object

Az előbbi program a kutya és macska osztállyal elég sok ismétlést tartalmaz. Elég lenne egy állat osztály (Animal), aminek lenne egy név beállítás és lekérdezés metódusa, és ezt egészítené ki két osztály, a kutya (Dog) és a macska (Cat), ami tud beszélni (talk). Ezt valósítjuk meg a második fejezetben.

Konstruktorok, a new és az initialize

Tanulmányozzuk a következő osztályokat és viselkedésüket

class Thing
  def set_name( aName )
    @name = aName
  end
 
  def get_name
    return @name
  end
end
 
class Treasure
  def initialize( aName, aDescription )
    @name         = aName
    @description  = aDescription
  end
 
  def to_s # override default to_s method
    "The #{@name} Treasure is #{@description}\n"
  end
end
 
thing1 = Thing.new
thing1.set_name( "A lovely Thing" )
puts thing1.get_name
 
t1 = Treasure.new("Sword", "an Elvish weapon forged of gold")
t2 = Treasure.new("Ring", "a magic ring of great power")
puts t1.to_s
puts t2.to_s
# Az inspect metódus, ami betekintést enged az objektumba
puts "Inspecting 1st treasure: #{t1.inspect}"

Van itt egy új metódus, az initialize, ami két argumentumot kap: @name és @description (név és leírás). Mikor egy osztálynak van initialize metódusa, a new példányosítás után ez fog lefutni. Ez azért jó, mert több objektum létrehozásakor nem kell minden tulajdonságnak értéket adni, hanem az initialize metódusban kaphatnak alapértelmezett értéket, másrészt pedig nem lesz sose olyan tulajdonsága az objektumoknak, ami ne kapna értéket (itt utaltam a someotherdog kérdéses nevére). Végül pedig felülírtuk a to_s metódust, amivel string formába alakítható egy objektum. A to_s metódusnév nem véletlen, ugyanis az összes ősosztálya a kincs osztálynak (Treasure) ezzel alakítja magát string formára. Azzal, hogy definiáltuk, felülírtuk a viselkedését a leszármazott osztályokban.

Objektumok megfigyelése

Láttuk, hogyan lehet betekinteni az osztályokba. A t1.inspect metódus teszi ember számára olvashatóvá a t1 objektumot.

#<Treasure:0x28962f8 @description="an Elvish weapon forged of gold", @name="Sword">

Ugyanezt érhetjük el a p() metódussal.

p( anobject )

Többek közt ezekről, és ezek részleteiről lesz szó a következő fejezetekben.