A Ruby programozási nyelv

Objektum-orientált programozás

1. Áttekintés

A Ruby egy objektumorientált programozási nyelv, azaz programjainak alapköve az osztály. A Ruby osztálykezelése sok szempontból eltér más objektumorientált nyelvektől. Számos olyan dolog hiányzik belőle, ami megvan a legtöbb nyelvben, azonban interpretált nyelv lévén olyan lehetőségeket is magában rejt, amelyek egy natív kódra fordító nyelvben (mint amilyen a C++), elképzelhetetlenek. A Ruby teljesen objektumorientált. Minden függvény egy osztály tagfüggvénye, még azok is, amelyeket látszólag osztályon kívül definiáltunk. Az osztályok olyan hierarchiába szerveződnek, amelynek a gyökere az Object osztály. Ez minden osztálynak őse. Minden függvény virtuális, tehát hiányoznak a nem virtuális tagfüggvények. Hiányzik a többszörös öröklődés, a konstans függvények, a tisztán virtuális függvények és szigorú értelemben a hozzáférhetőség sem szabályozható. További sajátosság, hogy a nyelvben minden objektum, még az osztályok is! Úgynevezett mixinek segítségével lehetőség van "visszafelé származtatásra", azaz előre megírhatunk egy leszármazást, amit tetszőleges ősosztályra alkalmazhatunk. A legérdekesebb tulajdonság azonban a reflexivitás: egy Ruby program "tud saját magáról", azaz ismeri a saját kódját, így azt futási időben meg is tudja változtatni. Lehetőség van osztályokhoz futási időben újabb függvényeket hozzávenni, sőt objektumszinten is lehet származtatni, azaz futási időben hozzá lehet adni egyetlen objektumhoz egy függvényt.

2. Alapok

Bevezetésképpen

Alkossunk egy nagyon egyszerű osztályt. Tegyük fel, hogy egy olyan vektorgrafikus programot kell írnunk, amely alkalmas téglalapok és körök megjelenítésére. Ehhez szükségünk lesz a (kétdimenziós) pontokat tároló Vector osztályra.

class Vector end

A class kulcsszót az osztály neve követi. Az osztály definícióját egy end zárja. A Rubyban az osztálynevek mindig nagybetűvel kezdődnek, ugyanis konstansnak kell lenniük. Ennek a furcsán hangzó kitételnek az az oka, hogy a Rubyban minden objektum, még az osztályok is. Az interpreter pedig elvárja, hogy az osztály típusú objektumok konstansok legyenek. Fentebb egy teljesen üres osztályt definiáltunk. Tegyük használhatóvá az osztályunkat, és egészítsük ki két adattaggal, amelyek a vektor koordinátáit tárolják, valamint egy konstruktorral, amellyel az osztály objektumai felinicializálhatók
class Vector def initialize( x, y ) @x = x @y = y end end

A konstruktor az initialize nevű függvény. Mivel a Rubyban nincs függvénytúlterhelés, ezért egy osztálynak csak egy konstruktora lehet. Ha több konstruktorra van szükségünk, akkor ezeket más nevű függvényekként kell megvalósítanunk, és explicite kell őket meghívnunk az objektumra. Az initialize függvény paraméterezése azonban nincs előre megszabva, így annyi paramétert kaphat, amennyit megszabunk. Mivel a Ruby szemétgyűjtéses nyelv, destruktor nem létezik benne. A @ prefixszel ellátott változók a tagváltozók. Csakúgy, ahogy a lokális változókat nem kell definiálni, a tagváltozók is akkor jönnek létre, amikor először értéket kapnak. A fenti példában tehát az osztályunknak két változója van (@x és @y), amelyek a vektor koordinátáit tárolják. A tagváltozók nem láthatók kívülről, ezért az osztály ilyen formájában még nem használható. Készítsünk getter műveleteket a koordinátákhoz.
class Vector def initialize( x, y ) @x = x @y = y end def x @x end def y @y end end

Már van egy használható osztályunk. Ideje példányosítani. Hozzuk létre a (2, 3) pont helyvektorát, és írassuk ki a koordinátáit!
v = Vector.new( 2, 3 ) puts v.x, v.y

Az eredmény:
2 3

Amint látjuk, osztályból példányt a statikus new függvényével lehet létrehozni. A new létrehozza a példányt, és meghívja rá az általunk megírt initialize függvényt. Az initialize megírása nem kötelező. Ha nem adjuk meg, akkor az objektum példányosításakor nem hívódik meg semmi. A self egy speciális objektum, amellyel minden objektumon belül saját magára hivatkozhatunk.

3. Beállító és lekérdező műveletek generálása

A jó objektumorientált stílus elfedi a tagváltozókat, és lehetetlenné teszi a elérésüket az osztályon (vagy még jobb esetben az objektumon) kívülről. Ez biztosítja az felület és a megvalósítás szétválását. Ha publikussá tesszük a változókat, akkor nem felügyelhetjük az írásukat, így az objektumok könnyen kerülhetnek inkonzisztens állapotba. Egy tagváltozó olvasása is problémás. Igaz, hogy ez az invariánst nem ronthatja el, viszont ha úgy döntünk, hogy kivesszük az osztályból, akkor az összes rá hivatkozó kód érvénytelen lesz, és az osztályon végzett lokális módosítás a teljes program refaktorálását teheti szükségessé. Gyakran van szükség azonban arra, hogy a változók értéket közvetlen módon lekérdezzük. A tagváltozók lekérdezésére általában lekérdező - vagy angolul getter - függvényeket szoktak írni, amelyek semmi mást nem csinálnak azon kívül, hogy visszatérnek a tagváltozó értékével. A lekérdező függvények írása azonban nagyon fárasztó és unalmas feladat, ráadásul potenciális hibalehetőség. Ennek kompenzálására a Ruby olyan metaprogramozási eszközöket tartalmaz, amelyek elvégzik a programozó helyett ezt a favágó munkát. Készítsük el a fent már megírt gettereket a Vector koordinátáihoz.

class Vector def initialize( x, y ) @x = x @y = y end attr_reader :x, :y end

Az attr_reader egy metaprogram, amely előállítja a kettőspont után álló változónevekhez tartozó getter műveleteket. Ez a Ruby nyelvnek nem egy lehetősége, hanem egy kiterjesztése. A
attr_reader :x

az alábbi kódot generálja:
def x; @x; end

Setter műveletet az attr_writer metaprogrammal generálhatunk. Ha egyszerre lenne szükségünk setter és getter műveletre, akkor használhatjuk a attr_accessor metaprogramot, amely mind a kettőt előállítja.
class Vector def initialize( x, y ) @x = x @y = y end attr_writer :x # x-et csak írni lehet kívülről. attr_accessor :y # y-t lehet írni és olvasni is. end v = Vector.new( 3, 3 ) v.x = 2 v.y = 4 puts v.y puts v.x # Hiba.

Összefoglalva a hozzáférhetőséget szabályozó metaprogramok a következő kódot generálják.
Metaprogram Generált kód
attr_reader :v def v; @v; end
attr_writer :v def v=(value); @v=value; end
attr_accessor :v attr_reader :v; attr_writer :v
attr_accessor :v, :w attr_accessor :v; attr_accessor :w

4. Hozzáférhetőség

A Rubyban a minden tagváltozó védett, és minden tagfüggvény nyilvános. A függvények hozzáférhetőségének megváltoztatása van mód a metaprogramok segítségével. Mivel azonban a tagváltozók hozzáférhetőségét még metaprogramokkal sem tudjuk megváltoztatni, ezért a továbbiakban a hozzáférhetőség csak a függvények hozzáférhetőségét jelenti. Jegyezzük meg továbbá azt is, hogy a statikus függvények (lásd később) hozzáférhetőségét sem lehet ezekkel a metaprogramokkal szabályozni. A C++ nyelvben három hozzáférhetőségi szintet különböztetünk meg. A nyilvános (public) tagok bárhonnan hozzáférhetők. A privát (private) tagok csak a saját osztályuk számára hozzáférhetők. Egy osztály védett (protected) tagjai elérhetők az illető osztályból és annak leszármazottaiból. A C++ csak az osztályszintű védelmet ismeri, objektumszintű védelem nincs. Egy adott osztály objektumai elérik egymás privát adattagjait is. A Rubyban ugyanezek a kifejezések teljesen mást takarnak, ezért a két koncepció összekeveredésének megelőzése érdekében a következő terminológiát vezetjük be: a hagyományos (C++-ban megszokott) hozzáférhetőségi szinteket a standard előtaggal látjuk el (standard nyilvános, standard privát, standard védett), a Ruby hozzáférhetőségi szintjeit pedig anélkül (nyilvános, privát, védett) nevezzük meg. (Megjegyzés: ez konyhajelölés, máshol nem találkozni vele.) A nyilvános függvények bárhonnan elérhetők, csakúgy, mint a standard nyilvánosak. A privát és védett függvények az osztályból és annak leszármazottaiból érhetők el. A privát függvényeket az különbözteti meg a védettektől, hogy nem kaphatnak explicit fogadót, azaz nem lehet őket megnevezett objektumokra meghívni. Még akkor sem, ha ez az objektum a self. Ebből az következik, hogy egy adott objektum privát függvényeit csak ő tudja meghívni, ráadásul csak saját magára, mert ha nincs explicit fogadó, akkor a fogadó implicite a self lesz. A védett függvények kaphatnak explicit fogadót, ezért azonosan működnek a standard védett függvényekkel. Mielőtt mélyebben belemennénk a dologba, tekintsük át az idevágó szintaktikát, hogy példákkal is illusztrálhassuk az előbb elmondottakat. A hozzáférhetőség szabályozásának kétféle szintaktikája van. Ha paraméterek nélkül írjuk le, akkor ugyanúgy működik, mint a C++-ban. Minden utána következő függvény (a legközelebbi minősítőig bezárólag) az adott hozzáférhetőségű lesz. Ha paramétert kapnak, akkor a definiálás helyétől függetlenül állítódik be a láthatóság. Ekkor használatuk hasonló az attr_reader használatához. A metaprogramok neve értelemszerűen: public, private és protected.

class X def privateFunction puts "X.private" end def protectedFunction puts "X.protected" end def callPrivate( other ) other.privateFunction end def callProtected( other ) other.protectedFunction end private :privateFunction protected :protectedFunction end

Nézzük meg, hogy mit csinál az alábbi kód.
a = X.new b = X.new a.callProtected( b ) a.callProtected( a ) a.callPrivate( a ) a.callPrivate( b )

Az első két sor gond nélkül lefut, mivel a protectedFunction védett, tehát más objektumok számára elérhető. A második két sor azonban nem fut le. A privateFunction privát, tehát nem lehet explicit objektumra meghívni. Az explicit objektum az első sorban a b volna, a második sorban pedig a, ami saját maga. Ennek ellenére nem lehet meghívni rá. Sőt az alábbi sem fut le:
class X # ... def callSelfPrivate self.privateFunction end # ... end

Itt is van explicit fogadó, tudniillik a self. Mivel azonban a self az alapértelmezett fogadó, ezért az alábbi kód lefut:
class X # ... def callSelfPrivate privateFunction end # ... end

Mind a privát, mind a védett függvények elérhetők a leszármazott osztályokból is, amint azt az alábbi kódrészlet demonstrálja. (Az Y osztály az X leszármazottja. Lásd később.)
c = Y.new c.callSuperPrivate c.callSuperProtected

Összefoglalva (nem teljesen precízen): a privát és a védett hozzáférhetőség között az a különbség, hogy a privát függvények objektumszintű védelmet is élveznek. A szekció elején esett szó a tagváltozók hozzáférhetőségéről. A pontosság kedvéért megjegyezzük, hogy tagváltozók a szó nemstandard jelentésében privátak. Azonban fontos különbség, hogy a változók nyelvi szinten privátak, nem pedig metaprogramok teszik őket azzá. Csak a teljesség kedvéért kerüljön ide a láthatóság-meghatározás másik szintaktikai konstrukciója is:
class X private def privateFunction puts "X.private" end protected def protectedFunction puts "X.protected" end public def callPrivate( other ) other.privateFunction end def callProtected( other ) other.protectedFunction end end

5. Statikus tagok

A statikus adattagokat @@ prefixszel kell ellátni. Definiálhatók bármelyik függvényben vagy magában az osztályban is. Rájuk is vonatkozik az a szabály, hogy ha nagybetűvel kezdődnek, akkor konstansok.

class Converter @@StaticArray = [ "zeroth", "first", "second" ] def getString( i ) @@StaticArray[ i ] end end converter = Converter.new puts converter.getString( 1 )

A képernyőn a "first" felirat jelenik meg. Ugyanezt írhattuk volna így is:
class Converter def getString( i ) @@StaticArray = [ "zeroth", "first", "second" ] @@StaticArray[ i ] end end

Statikus attól lesz egy tagfüggvény, hogy a definiálásakor a self. előtaggal látjuk el. A fenti példában például a konvertáló függvény is logikusan statikus, tehát érdemes lenne átírni:
class Converter def self.getString( i ) @@StaticArray = [ "zeroth", "first", "second" ] @@StaticArray[ i ] end end puts Converter.getString( 1 )

A statikus függvényeket az osztálynevükkel prefixálva lehet elérni az osztályon kívülről.

6. Származtatás

Osztályok neve után egy < jel után adható meg az osztály, melyből származtatni szeretnénk. Minden metódus virtuális metódusként viselkedik. Származtatásnál csak a metódusok öröklődnek, az adattagok NEM, mivel azok mindig objektumokhoz vannak kötve. Természetesen a metódusok láthatóságai is öröklődnek. Az adattagok öröklésének érzését tudjuk kelteni, ha a leszármazott konstruktorában meghívjuk az ős konstruktorát (feltéve, hogy az ős a konstruktorában inicializálta az adattagjait). Többszörös öröklődés nem megengedett a nyelvben, erre az úgynevezett mixin modulok használhatók.

class Person def initialize(nev) @nev=nev end end class Employee < Person def initialize(nev,fizetes) super(nev) @fizetes=fizetes end end

7. Objektumok mint paraméterek (duck typing)

A függvények paramétereinek nincs explicit típusa. Honnan tudja előre az interpreter, hogy milyen függvényeket lehet majd meghívni rá, amikor átadjuk neki az aktuális paramétert? A válasz az, hogy sehonnan. Az, hogy a paraméternek milyen függvényei vannak, akkor dől el, amikor átadódik. Ez teljesen más filozófia, mint amit C++-ban megszokhattunk. C++-ban előre meg kell mondanunk a fordítónak, hogy egy paraméternek mi lesz a típusa, amikor átadódik. Innen tudja a fordító, hogy milyen függvényeket lehet meghívni rá. Más típusú paramétert nem adhatunk a függvénynek, még akkor sem, ha volna olyan nevű függvénye. Az Ruby-interpreter viszont a hívás idejében vizsgálja meg, hogy létezik-e a megfelelő nevű és paraméterszámú függvény, és ha létezik, akkor engedi meghívni. A C++-ban típus szerinti, a Rubyban viszont név szerinti ellenőrzés történik. Az elnevezés James Whitcomb Riley "kacsatesztjére" utal, mely szerint: "Ami úgy jár, mint egy kacsa, és úgy hápog, mint egy kacsa, az egy kacsa." Tehát két azonos interfészű objektum egymással helyettesíthető, még akkor is, ha nem a közös interfész alapján hivatkozunk rájuk. Az alábbi kódrészlet szemlélteti a jelenséget.

class X def print puts "X.a" end end class Y def print puts "Y.a" end end def print( object ) object.print end print( X.new ) print( Y.new )

Mindkét osztálynak van print nevű függvénye. Az X és Y osztályoknak van ugyan közös ősosztálya (pl. az Object), de ebben nincs benne a print, vagyis a közös interfészük. A két print függvénynek csak a neve és a paraméterszáma azonos, más közös tulajdonságuk nincs. Ennek ellenére mindkét osztály objektumát át lehet adni az osztályokon kívüli printnek, mert a futáshoz elég a névegyezés. Ugyanennek a kódnak a C++-beli megfelelője le sem fordulna, mert nem tudunk olyan ősosztályt mondani, amelyben benne van az a függvény.

8. Objektumok a háttérben

A Ruby programozási nyelv tisztán objektum-orientált programozási nyelv, ami alatt azt kell érteni, hogy maradéktalanul megvalósítja az objektum-orientált paradigmát. Ennek lényege, hogy a programunk objektumok halmaza, és az interakciójuk egymásnak küldött üzenetek formájában valósul meg.
Minden osztály a közös Object osztályból származik.
Továbbá a Ruby egy interpretált nyelv, azaz minden sor bevitel után végre is hajtódik. Ez lehetővé teszi, hogy egy osztálydefiníció lezárásával már dolgozhassunk az adott osztállyal.
A következő néhány szakasz ezek megvalósításával foglalkozik, így segíthet olyan problémák megoldásában, amikor "rejtélyesen" eltűnnek üzenetek, vagy nem a megfelelő osztály kapja meg az üzenetet. Ezek nagyon ritkán fordulhatnak elő, azonban ha értjük hogyan is "reprezentálódik" egy osztály, akkor könnyebben írhatunk hibátlan programokat.

9. Hogyan működnek együtt az osztályok és objektumok

Amikor befejezünk egy osztálydefiníciót, akkor az interpreter létre is hoz nekünk egy objektumot, amit a továbbiakban singleton osztálynak nevezünk. Megtévesztő lehet, de a singleton osztály lényegében egy objektum, aminek speciális adattagjai vannak, és tartalmazza az osztályra specifikus metódusok egy részét. Az, hogy pontosan melyik metódus kerül a singleton osztályba, az később derül ki. Tekintsük a következő ábrát: (Nem UML diagram) Egy egyszerű objektum az osztályával és ősosztályával Létrehoztunk egy gitár osztályt, aminek van egy Play() metódusa. A háttérben jelen van az Object singleton osztály, majd létrejön a Guitar singleton osztály. A super adattag az ősosztály singleton objektumára mutat. A klass pedig az objektum singleton osztályára. Mivel így létrejöttek a megfelelő objektumok, így értelemes a Guitar.new üzenet elküldése.
Példányosítunk egy lucille objektumot. Bár a példában nem szerepel, a Guitar objektum tartalmazza a saját attribútumait.
Mivel a klass és super mutatókon keresztül meghatározott az öröklési és asszociatív kapcsolat, ezért a lucille-nek küldött üzenetek végigkövethetők a "gyökérig". Például:

  1. Ha elküldjük a lucille.play() üzenetet, akkor a Ruby megnézi a fogadót (lucille), majd a klass referenciát követve eljut a Guitar singleton osztályig, ott végignézi a metódustáblát, megtalálja a Play()-t és meghívja azt.
  2. Ha a lucille.display() üzenetet küldjük, akkor az előző ponthoz hasonlóan itt is eljut a Guitar singleton osztály metódustáblájáig, azonban ott nem találja a display() metódust, így a super referencián keresztül eljut az Object singleton osztályig, ahol a metódustábla tartalmazza a keresett metódust.

10. Meta osztáyok

Amikor hozzáadunk egy osztálymetódust az osztálydefinícióhoz, akkor a fenti modellbe ezt nem tudjuk beleilleszteni, mivel a singleton osztály az egyes objektumokon végezhető metódusokat tartalmazza. Így létrejön egy úgynevezett metaosztály, ami az osztályszintű metódusokat és adattagokat hivatott kezelni. Egészítsük ki az előbbi példát. Metaosztályok elhelyezkedése Tehát megjelenik a Guitar' metaosztály, amit virtuálisnak jelölünk. (A flags adattag kiegészül egy V értékkel. A külvilág számára az a legfontosabb különbség, hogy ezek az osztályok kívülről láthatatlanok. Nem jelennek meg olyan objektumlistákban, mint a Module#ancestor és ObjectSpace.each_object visszatérési értékei.)
Tehát, ha elküldjük a Guitar.strings() üzenetet, akkor a Guitar lesz a fogadó, ennek a klass referenciáját követve jutunk el a keresett metódusig.
Tehát a Guitar' a Guitar metaosztálya. De mivel a Guitar az Object leszármazottja, így a metaosztálya (Guitar') az Object metaosztályának (Object') leszármazottja lesz.

11. Objektumokból osztályt

Rubyban lehetőség van objektumokból létrehozni új osztályokat, azaz objektumokból származtatni. Példánkban két string objektumot hozunk létre, ezután egy névtelen osztályt hozunk létre az a Objektumból, majd ehhez az új osztályhoz hozzáadunk néhány új metódust.

a = "hello" b = a.dup class >>a def to_s "The value is '#{self}'" end def two_times self + self end end a.to_s a.two_times b.to_s

Kimenet:
"The value is 'hello'" "hellohello" "hello"

A példában a <<obj jelölést használtuk, ami annyit jelent, hogy "készíts nekem egy olyan osztályt, mit az obj-é". Az eredményt megfigyelhetjük a következő ábrán: Objektumból öröklés Az új metódusok nem az eredeti osztályt módosítják, hiszen arra még lehet szüksége más objektumoknak is, ezért bevezetnek egy virtuális singleton osztályt, ami megvalósítja az új műveleteket. Mivel a láncban előbb szerepelnek ezek a műveletek, így jól szimulálják a kibővített viselkedést. Rubyban az osztályok soha nem zárulnak le, tehát bármikor újabb metódusokkal és adattagokkal bővíthetjük a meglévő osztályokat. Akár a beépített osztályokat is. Ugyanez vonatkozik a virtuális osztályokra (lásd az előző részben). Ha egy objektum klass referenciája már egy létező virtuális osztályra mutat, akkor nem jön létre új virtuális osztály, hanem a létező virtuális osztályt bővíti ki az interpreter.

12. Mixin modulok

Amikor egy osztály tartalmaz egy modult, akkor a modul példánymetódusai az osztály példánymetódusai lesznek. Majdnem olyan, mintha a modul az osztály ősosztálya lenne. Nem meglepő, hiszen pont így valósítják meg. Amikor include-olunk egy modult, a Ruby létrehoz egy névtelen proxy osztályt, ami a modulra hivatkozik. Majd beékeli a proxy osztályt, mint az include-oló osztály ősosztályát. A proxy osztály referenciákkal hivatkozik a modul adattagjaira és metódusaira. Ez fontos, mivel a modult beinclude-olhatjuk több osztályba és több különböző öröklési láncban is feltűnhet. Azonban hála a proxy osztálynak, ha módosítjuk a modult, akkor az minden érintett osztályban is módosul, mivel "fizikailag" csak egy modul van jelen. Ezt szemlélteti a következő ábra is. osztály kiegészítése Ha több modult include-olunk, akkor azok az include-olás sorrendjében kerülnek be az öröklési láncba. Ha a modul szintén tartalmaz modulokat, akkor azok is proxy-osztályokként lesznek befűzve az öröklési láncba.

13. Az osztályok nevei konstansok

Amikor egy osztálymetódust hívunk meg, akkor a singleton osztálynak küldünk üzenetet. Tehát amikor elküldjük a String.new("gunby") üzenetet, akkor a fogadónak tudni kell, hogy létezik valahol egy String singleton osztály. Ezt úgy éri el, hogy amikor létrehozunk egy osztályt (legyen az akár beépített, akár felhasználói), akkor létrejön egy konstans referencia, ami a singleton osztályra mutat, ugyanolyan névvel, mint az osztály.
Éppen ezért mondhatjuk, hogy az osztálynevek konstansok, és a következetesség miatt kell minden osztály nevének nagybetűvel kezdődjön. A tény, hogy az osztályok nevei konstansok, azt jelenti, hogy ugyanúgy tekinthetjük őket, mint bármilyen más Ruby objektumot: másolhatjuk őket, átadhatjuk metódusnak, kifejezésekben használhatjuk őket.
Így könnyen készíthető el egy Gyár (tervminta):

def factory(klass, *args) klass.new(*args) end factory(String, "Hello") factory(Dir,".") flag = true (flag ? Array : Hash)[1, 2, 3, 4] flag = false (flag ? Array : Hash)[1, 2, 3, 4]
Kimenet:
"Hello" #>Dir:0x1c90e4< [1, 2, 3, 4] {1=>2, 3=>4}

14. A legfelső szintű végrehajtó környezet

Sokszor emlegettük már, hogy a Rubyban minden objektum, illetve minden utasítás egy objektum része. De vajon mi van a legfelső szinten lévő parancsvégrehajtóval? Ha azt írjuk egy script elején, minden osztálydefiníción kívülre, hogy

puts "Hello, World"
akkor az milyen objektumnak lesz a része?
A válasz egyszerű. A self.class eredményének, az Object-nek lesz a része. Itt is objektumok interakciója zajlik, ugyanis a "Hello world" egy String objektum, a puts pedig az Object egy metódusa. Az itt bevezetett változók is az Object részei lesznek.

15. Objektumok fagyasztása

Néha nagyon sok munka egy objektumot a megfelelő állapotba hozni, és nem szeretnénk, ha valaki megváltoztatná. Néha úgy kell átadni egy objektumot két objektum között, hogy az átvitelt egy harmadik fél által készített objektum végzi. Ekkor azt szeretnénk, hogy ne változzon meg az objektum. Ez azért nagy probléma, mert a Rubyban az osztályok soha nem zárulnak le, bármikor, bárhol módosíthatók. A Ruby biztosít egy eszközt ennek megoldására. Bármelyik objektum lefagyasztható az Object#freeze metódussal. Egy lefagyasztott objektumon nem lehet változtatni, nem lehet az adattagjait módosítani. Ezen úgy lehet segíteni, hogy az objektumot lemásoljuk egy másik objektumba, de úgy, hogy a lefagyasztott adattagot nem másoljuk. Erre szolgál a dup metódus. Ha a clone metódussal másoljuk az objektumot, akkor a fagyasztás továbbra is megmarad, így az új objektumot sem tudjuk módosítani.

str1 = "hello" str1.freeze str1.frozen? str2 = str1.clone str2.frozen? str3 = str1.dup str3.frozen?

"hello" true true false