1.4.1.12. fejezet, Modulok és Mixinek
A Ruby osztály hierarchiája szerint egy ősosztálynak több leszármazottja van, és egy leszármazottnak egy ősosztálya. Az osztályok egymás metódusait és tulajdonságait szigorú szabályok szerint vehetik igénybe. Van olyan helyzet, amikor mégis jó lenne ezeket a szabályokat kibővíteni. Például egy kard lehet fegyver vagy antik kincs. Egy PC lehet számítógép, vagy egy formája egy befektetésnek (angolul). Ezt a kibővítést már gyakoroltuk az objektum metódus készítéssel, azonban ez más megoldás.
A modul és az osztály meghatározása igen közel állnak egymáshoz. A Module osztály közvetlen szülője a Class osztálynak. A Modul is tartalmazhat konstansokat, metódusokat és osztályokat.
module MyModule GOODMOOD = "happy" BADMOOD = "grumpy" def greet return "I'm #{GOODMOOD}. How are you?" end end
Az osztálymetódusokhoz hasonlóan tartalmazhat modul metódust.
def MyModule.greet return "I'm #{BADMOOD}. How are you?" end
Azonban van két tulajdonság, amiben különböznek. Az osztálynak lehet példánya és szülője, Class osztálynak lehet példánya (objektumok), szülői hierarchia (superclass), gyermekei (subclasses), a moduloknál mindez nem lehetséges. És itt jön az első kérdés: ha nem hozható létre belőle példány, mire jó akkor? Erre válaszul két szó van: namespace és mixins. Ruby mixins módszere a több ősosztályos hierarchia előnyeinek kiaknázása. Nemsokára megvizsgáljuk közelebbről, de először nézzük meg mi az a névtér (namespace).
Modulok mint névterek
Lehet úgy értelmezni a modulokat, mint a konstansok, osztályok és metódusok egybefonódása. A modulon belül találkozhatnak a kód különböző részei, hasznosítva ugyanazt a névteret, amin belül láthatják egymást, de láthatatlanok a névtéren kívülről. A Ruby definiál néhány névteret, úgy mint a Kernel és a Math is. A Math modult tartalmazza a matematikai műveleteket, úgy mint a négyzetre emelés, négyzetgyökvonás, definiálja néhány konstanst, mint pl. a PI. A Kernel modul definiál metódusokat, amiket kívülről használunk, mint pl.: print, puts és gets.
Nézzük a kódot, amit korábban láttunk.
module MyModule GOODMOOD = "happy" BADMOOD = "grumpy" def greet return "I'm #{GOODMOOD}. How are you?" end def MyModule.greet return "I'm #{BADMOOD}. How are you?" end end
A konstansok eléréséhez, mint az osztályoknál, használhatjuk a szkóp (::) operátort.
puts(MyModule::GOODMOOD)
Metódus eléréséhez használhatjuk az osztályoknál megszokott pont jelet
puts( MyModule.greet )
De hogyan érjük el a "példány" metódust? A modul ugyanis zárt névtér, ezért hibás a
puts( MyModule.greet )
használata. Ha modul helyett egy osztály lenne, előállíthatnánk belőle egy példányt a new metódussal, és máris elérhető lenne.Ez az a hely, ahol megjelenik a mixin
Beemelt modulok, vagy mixinek
Egy objektum a modul metódusait a beemeléssel érheti el.
include MyModule puts ( greet )
Megjegyzem, hogy csak a példány metódusok érhetők így el, a modul metódusok (MyModule.greet) továbbra is a modul megnevezéssel. Ha egy osztály definíción belül emeljük be a modult, annak az objektum metódusai az osztály definíció részévé válik, és ezentúl hívható az osztályon belül, vagy az osztályból készült objektumon keresztül.
class MyClass include MyModule def sayHi puts( greet ) end end
ob = MyClass.new ob.sayHi puts(ob.greet)
Ez a többszöri öröklődés kiváltásának egyik módja a Ruby-ban. Visszatérve a fejezet elején említett hasonlóságra, nézzük hogyan definiálhatjuk a kardot, mint fegyvert és mint antik kincset a Ruby-ban.
module MagicThing attr_accessor :power end module Treasure attr_accessor :value attr_accessor :owner end class Weapon attr_accessor :deadliness end class Sword < Weapon include Treasure include MagicThing attr_accessor :name end
Elkészült a kard osztály, hozzunk belőle létre egy példányt, és töltsük fel adatokkal.
s = Sword.new s.name = "Excalibur" s.deadliness = "fatal" s.value = 1000 s.owner = "Gribbit The Dragon" s.power = "Glows when Orcs Appear"
Ezután a tulajdonságok kiiratása gyerekjáték már:
puts(s.name) puts(s.deadliness) puts(s.value) puts(s.owner) puts(s.power)
Megjegyzem a lokális változók kívülről nem látszanak.
x = 1 # local to this program module Foo x = 50 # local to module Foo # This can be mixed in but the variable x won't then be visible def no_bar return x end def bar @x = 1000 return @x end puts( "In Foo: x = #{x}" ) # Ez eléri a "module local" x -et (50) end include Foo puts(x) # Lokális x = 1 puts( no_bar ) # Hiba! Ez a metódus nem éri el a modul lokális változóját puts(bar)
A példány változók látszanak (mint a bar metódus mutatja), a lokálisak nem. Modul rendelkezhet saját lokális változóval, ami az object objektum alá fog tartozni.
module X @instvar = "X's @instvar" def self.aaa puts(@instvar) end end X.aaa #=> "X‟s @instvar"
De a példány változók, amire a belső objektumok hivatkoznak, annak a modulnak a látókörébe tartoznak, ahova beemeltük a modult.
module X @instvar = "X's @instvar" def amethod @instvar = 10 # creates @instvar in current scope puts(@instvar) end def self.aaa puts(@instvar) end end include X X.aaa #=> X's @instvar puts( @instvar ) #=> nil amethod #=> 10 puts( @instvar ) #=> 10 @instvar = "hello world" puts( @instvar ) #=> "hello world"
Az osztályváltozók bekerülnek a modul hatókörébe, és ugyan úgy megváltoztathatók, mint a lokális változók.
Most tegyük külön fájlba a modulunkat.
X.rb
module X @@classvar = "X's @@classvar" end puts "included module X"
classvars.rb
Itt figyeljünk fel a require_relative metódusra, amivel az aktuális könyvtárban található metódust érhetjük el.
@x = 1 # local to this program require_relative 'X' include X puts(@@classvar) @@classvar = "bye bye" puts( @@classvar )
Megnézhetjük az instance_variables metódussal, mit végeztünk:
p( X.instance_variables ) p( self.instance_variables )
Eredmények:
included module X
X's @@classvar
bye bye
[]
[:@x]
Egy szál példány változónk van, a main objektumban (@x).
Névütközések
Előfordulhat, hogy metórusok, változók, tulajdonságok azonos néven szerepelnek egy-egy modulban. Például:
Sad.rb
module Sad def Sad.mood # module method return "sad" end def expression # instance method return "frowning" end end
Happy.rb
module Happy def Happy.mood # module method return "happy" end def expression # instance method return "smiling" end end
class Person require_relative 'Happy' require_relative 'Sad' include Happy include Sad attr_accessor :mood def initialize @mood = Happy.mood end end person = Person.new puts(person.expression)
Itt az inicializálás módosítja a mood tulajdonságát az objektumnak, ami meghívja a mood_write metódust. Ezt mindkét modul tartalmazza. Az inicializálásnál konkrétan meghatároztuk, hogy a Happy modul mood string értékét kívánjuk beállítani, ezért ez nem ütközik semmivel. De mindkét modul tartalmazza az expression metódust, felmerül a kérdés, hogy melyiket használjuk amikor meghívjuk? Azt várnánk, hogy az utoljára definiált metódus lesz az, jelen esetben a Sad modul metódusa. És jól sejtettük, így is történt. Ha megfordítjuk az include sorrendjét, megváltozik a metódus, és a Happy expression-ja hívódik meg. A konfliktus kezelésre a megoldás az álnév, az alias használata.
Álnevek
Happy.rb
module Happy def Happy.mood # module method return "happy" end def expression # instance method return "smiling" end alias happyexpression expression end
Sad.rb
module Sad def Sad.mood # module method return "sad" end def expression # instance method return "frowning" end alias sadexpression expression end
Meghívásuk pedig egyszerű:
person = Person.new puts(person.happyexpression) puts(person.sadexpression)
Az eredmény pedig:
smiling frowning
De vigyázzunk modulok és a mixin-ek használatával. Olyan nagyon elbonyolítható egy program, hogy az már szörnyű.
module MagicThing # module class MagicClass # class inside module end end module Treasure # module end module MetalThing include MagicThing # mixin class Attributes < MagicClass # subclasses class from mixin end end include MetalThing # mixin class Weapon < MagicClass # subclass class from mixin class WeaponAttributes < Attributes # subclass end end class Sword < Weapon # subclass include Treasure # mixin include MagicThing # mixin end
Modulok beemelése fájlból
Eddig egy fájlban tároltuk a modult és az osztályt, amibe beemeltük. Az utóbbi néhány mintában próbáltam becsempészni egy kis fájl kezelést. A szükséges metódusunk a
require( "testmod.rb" )
A kiterjesztés többnyire elhagyható, hacsak nem .rb kiterjesztésű a fájl. A require_relative az aktuális könyvtárban is keresi a megadott állományt. Ha külön könyvtárba helyezzük el a fájlokat, akkor használjuk a
$: << "C:/mydir"
metódust. A $: tömb tartalmazza a keresési útvonalakat.
A require mellett használható a load metódus is. Néhány különbség azért van a kettő közt. Például a load második paraméterként egy boolean változó értéket is fogad, ami arra szolgál ha igaz értéket adunk át, hogy lefuttassa a fájlban leírt programrészt mint egy névtelen modul. Ez a dinamikus programozáshoz tartozik, mivel generált kód is futtatható vele. A másik különbség, hogy a require csak egyszer tölti be a modult, akárhányszor is hivjuk meg, míg a load annyiszor, ahányszor kérjük.
Modulok és osztályok
Láttuk miről ismerjük meg a modult, most nézzük meg igazából mi is a Module class. Minden megnevezett osztály egy példánya a Module osztálynak. Azonban nem készíthetünk leszármaztatást egy modulból, habár Module osztályból leszármazott készíthető:
class X < Module end
De például a Class osztály egy leszármazottja a Module osztálynak.
Class Module #=> is the superclass of Class Object #=> is the superclass of Module
A következő modulok vannak beépítve a Ruby értelmezőbe:
Comparable, Enumerable, FileTest, GC, Kernel, Math, ObjectSpace, Precision, Process, Signal
Comparable egy mixin modul, ami megengedi az osztályoknak, hogy implementálják az összehasonlítás metódusokat
Enumerable egy mixin modul a megszámlálásra. Ennek a beemelésével az osztálynak implementálnia kell a az each metódust.
FileTest egy modul ami fájl tesztelő funkciókat tartalmaz, amit a File osztály osztály-metódusaival érhetünk el.
GC modul egy felületet biztosít a Ruby számára a szemét gyűjtésre, a használaton kívüli objektumok által foglalt memória felszabadítására.
Kernel az Object osztály által beemelt modul, ami definiálja a Ruby 'built-in' metódusait.
Math modul alapvető trigonometriai és határozatlan (transcendental) függvényeket tartalmaz. Mindegyiknek van példány és modul metódusa, azonos definícióval és névvel.
ObjectSpace modul, aminek rutinjai a szemétgyűjtéssel együttműködnek, és megengedi a programozónak, hogy minden élő objektumon végiglépkedjen.
Precision egy mixin, konkrét valós számosztályokkal. Itt a 'precizitás' kifejezés a valós számhoz közelítés finomságát határozza meg (törtrész). Ez az osztály nem keverendő össze, és nem emelhető be olyanba, ami nem a Real része (másszóval nem emelhető be olyan osztályokba és leszármazottaikba, mint a Complex vagy Matrix)
Process a processzek manipulálására szolgáló modul. Összes metódusa modul-metódus.
Signal az a modul, ami jelzéseket küld a futó processzeknek. A listája és jelentése a jelzéseknek implementáció függő.
Hatáskörök
A dupla kettőspont a hatáskörben(scope) meghatározó szerepet játszik.Pl.:
TOPLEVELCONST="Top leve0" module OuterMod module InnerMod class Class1 ModConst="const0" def Class1.aMethod puts(::TOPLEVELCONST) end end end end op=OuterMod::InnerMod::Class1.new OuterMod::InnerMod::Class1::aMethod p(op.class)
Ha szkóp jellel kezdünk egy kifejezést, az a legfelső szinten definiált konstanst jelenti.
Modul funkciók
Ha azt szeretnénk, hogy modul és példány metódusként is elérhető legyen a függvényünk, használhatjuk a module_function metódust, egy szimbólummal, ami a metódus nevét jelzi:
module MyModule def sayHi return "hi!" end def sayGoodbye return "Goodbye" end module_function :sayHi end class MyClass include MyModule def speak puts(sayHi) puts(sayGoodbye) end end ob = MyClass.new ob.speak puts(MyModule.sayHi) puts(ob.sayHi)
Eredménye:
hi! Goodbye hi!
Objektumok bővítése
Egy objektumot metódussal bővíteni tudunk már, most mégis nézzük meg az extend metódus működését, ami modul függvényekkel bővíti az objektumot.
module A def method_a puts( 'hello from a' ) end end class MyClass def mymethod puts( 'hello from mymethod of class MyClass' ) end end ob = MyClass.new ob.mymethod ob.extend(A) ob.method_a
Kimeneti értékek
hello from mymethod of class MyClass
hello from a
Egyszerre több metódussal is bővíthető egy objektum.
ob.extend(B, C)
Ha azonos nevű metódust talál, az új metódus lép az előző helyébe.
module A def method_a puts( 'hello from a' ) end end class MyClass def mymethod puts( 'hello from mymethod of class MyClass' ) end end module C def mymethod puts( 'hello from mymethod of module C' ) end end ob = MyClass.new ob.mymethod ob.extend(A) ob.method_a ob.extend(C) ob.mymethod
Eredménye:
hello from mymethod of class MyClass
hello from a
hello from mymethod of module C
Egy objektum bővítése megtiltható a freeze metódusának meghívásával:
ob.freeze
ill. lekérdezhető az objektum lefagyasztottsága (frozen?)
if !(ob.frozen?) ob.extend( D ) ob.method_d else puts( "Can't extend a frozen object" ) end
- A hozzászóláshoz be kell jelentkezni