|
|||
Forrige < |
Innhold ^
|
Neste >
|
sin
, cos
, og så videre. Du kan putte dem
alle inn i en fil, trig.rb
, slik at framtidige generasjoner
kan nyte den. I mellomtiden jobber Sally på en simulering av det gode
og det onde, og lager kode-sett med hennes egne nyttige rutiner,
inkludert beGood
og sin
, og legger dem i
action.rb
. Joe, som vil skrive et program som viser hvor
mange engler som kan danse på et knappenålshode, trenger å laste
både trig.rb
og action.rb
inn i sitt program. Men
begge definerer en metode som kalles sin
. Dårlige nyheter.
Svaret er modul-mekanismen.
Moduler definerer et navnerom (namespace), en sandkasse
hvor dine metoder og konstanter kan leke uten å trenge å være bekymret
for å bli trampet på av andre metoder og konstanter. Trig-funksjonene
kan gå inn i en modul:
module Trig PI = 3.141592654 def Trig.sin(x) # .. end def Trig.cos(x) # .. end end |
module Action VERY_BAD = 0 BAD = 1 def Action.sin(badness) # ... end end |
require
utsagn, som diskuteres
på side 103) og referere til de kvalifiserte navnene.
require "trig" require "action" y = Trig.sin(Trig::PI/4) wrongdoing = Action.sin(Action::VERY_BAD) |
module Debug
|
||
def whoAmI?
|
||
"#{self.type.name} (\##{self.id}): #{self.to_s}"
|
||
end
|
||
end
|
||
class Phonograph
|
||
include Debug
|
||
# ...
|
||
end
|
||
class EightTrack
|
||
include Debug
|
||
# ...
|
||
end
|
||
ph = Phonograph.new("West End Blues")
|
||
et = EightTrack.new("Surrealistic Pillow")
|
||
|
||
ph.whoAmI?
|
» |
"Phonograph (#537762134): West End Blues"
|
et.whoAmI?
|
» |
"EightTrack (#537761824): Surrealistic Pillow"
|
Debug
-modulen, vil både Phonograph
og
EightTrack
få tilgang til whoAmI?
instansmetoden.
Et par punkter angående include
-utsagnet før vi går videre. Først det har ingenting å gjøre
med filer. C-programmmerere bruker et preprosessor-direktiv kalt #include
for å legge inn
innholdet i en fil inn i en annen under kompilering. Rubys include
-utsagn lager ganske enkelt
en referanse til en navngitt modul. Hvis den modulen er i en separat fil, må du benytte require
for å dra denne filen inn før du bruker include
. For det andre, et Ruby include
kopierer ikke bare modulens instansmetoder inn i klassen. Isteden så lages det en referanse fra
klassen til den inkluderte modulen. Hvis multiple klasser inkluderer den modulen, vil de alle peke til
den samme tingen. Hvis du forandrer definisjonen på en metode innen en modul, selv mens programmet
kjører, vil alle klassene som inkluderer den modulen utføre den nye oppførselen.[Selvfølgelig
snakker vi kun om metoder her. Instansvariabler er alltid per-objekt, for eksempel.]
Mixins gir deg en flott, kontrollert måte å gi funksjonalitet til klasser. I midlertid kommer
deres virkelige styrke når koden i mixinen starter å interagere med kode i klassen som benytter den.
La oss ta en standard Ruby-mixin Comparable
som eksempel.
Comparable
-mixinen kan bli benyttet til å legge til sammenlignings-operatorer
(<
, <=
, ==
, >=
, and >
), så vel som metoden
between?
, til en klasse. For å få dette til å fungere forutsetter
Comparable
at alle klasser som bruker den definerer operatoren
<=>
. Og da som en klasse-skribent, vil du definere en metode <=>
,
inkludere Comparable
,og få 6 sammenlignings-funksjoner gratis. La oss prøve det med vår
Song
-klasse ved å gjøre sangene sammenlignbare basert på deres varighet. Alt
vi må gjøre er å inkludere Comparable
-modulen og implementere
sammenlignings-operatoren <=>
.
class Song include Comparable def <=>(other) self.duration <=> other.duration end end |
song1 = Song.new("My Way", "Sinatra", 225)
|
||
song2 = Song.new("Bicylops", "Fleck", 260)
|
||
|
||
song1 <=> song2
|
» |
-1
|
song1 < song2
|
» |
true
|
song1 == song1
|
» |
true
|
song1 > song2
|
» |
false
|
inject
-funksjon,
implementerte den innen klassen Array
. Vi lovte da å gjøre det mere generelt
brukbart. Hvilken måte bedre da en å gjøre det til en mixin-modul?
module Inject def inject(n) each do |value| n = yield(n, value) end n end def sum(initial = 0) inject(initial) { |n, value| n + value } end def product(initial = 1) inject(initial) { |n, value| n * value } end end |
class Array
|
||
include Inject
|
||
end
|
||
[ 1, 2, 3, 4, 5 ].sum
|
» |
15
|
[ 1, 2, 3, 4, 5 ].product
|
» |
120
|
class Range
|
||
include Inject
|
||
end
|
||
(1..5).sum
|
» |
15
|
(1..5).product
|
» |
120
|
('a'..'m').sum("Letters: ")
|
» |
"Letters: abcdefghijklm"
|
Enumerable
-modulen, som starter på side 403.
self
.
For en mixin, betyr dette at modulen du mikser inn i din klient-klasse kan
skape instansvariabler i klient-objektet og kan bruke attr
og venner for å definere accessors for disse instansvariablene. For eksempel:
module Notes attr :concertA def tuning(amt) @concertA = 440.0 + amt end end class Trumpet include Notes def initialize(tune) tuning(tune) puts "Instance method returns #{concertA}" puts "Instance variable is #{@concertA}" end end # The piano is a little flat, so we'll match it Trumpet.new(-5.3) |
Instance method returns 434.7 Instance variable is 434.7 |
module MajorScales def majorNum @numNotes = 7 if @numNotes.nil? @numNotes # Return 7 end end module PentatonicScales def pentaNum @numNotes = 5 if @numNotes.nil? @numNotes # Return 5? end end class ScaleDemo include MajorScales include PentatonicScales def initialize puts majorNum # Should be 7 puts pentaNum # Should be 5 end end ScaleDemo.new |
7 7 |
@numNotes
. Uheldigvis var resultatet antagelig ikke hva
forfatteren ville.
For det meste prøver ikke mixin-moduler å bære sine egne instansdata
rundt---de bruker accessors for å få tak i data fra klient-objektet. Men
hvis du trenger en mixin som trenger å ha sin egen tilstand, forsikre deg
om at instansvariabelene har unike navn for å skille dem fra andre mixins
i systemet(kanskje ved å bruke modulens navn som en del av variabel-navnet).
Enumerable
.
Alt du trenger å gjøre er å skrive en iterator kalt each
,
som returnerer alle elementer i din samling etter tur. Mix inn
Enumerable
, og plutselig støtter din klasse
slike ting som map
,include?
, og find_all?
.
Hvis objektene i din samling implementerer en meningsfull ordering semantikk ved
å bruke <=>
-metoden, vil du også få min
, max
,
og sort
.
load "filename.rb" require "filename" |
load
-metoden inkluderer den navngitte Ruby-kildefilen hver gang metoden
blir eksekvert, mens require
laster en hvilken som helst gitt fil bare en
gang.
require
har tilleggs-funksjonalitet: den kan laste delte binære biblioteker.
Begge rutiner aksepterer relative og absolutte stier. Hvis gitt en relativ sti (eller bare
et enkelt navn), vil de søke hver katalog i nåværende sti ($:
, diskutert på
side 140) for filen.
Filer som lastes ved å bruke load
og require
kan selvfølgelig laste
andre filer, som inkluderer andre filer og så videre. Hva som ikke ser åpenbart
ut er at require
er et eksekverbart utsagn---de kan være inn i et if
-
utsagn, eller det kan inkludere en streng som akkurat ble laget. Søkestien kan bli endret
under kjøring også. Bare legg til den katalogen du vil til strengen $:
.
Siden load
vil inkludere kildekoden betingelsesløst, kan du bruke det til å
innlaste en kildefil som kan ha forandret seg siden programmet startet:
5.times do |i| File.open("temp.rb","w") { |f| f.puts "module Temp\ndef Temp.var() #{i}; end\nend" } load "temp.rb" puts Temp.var end |
0 1 2 3 4 |
$Log: tut_modules.xml,v $ Revision 1.8 2002/07/23 13:07:51 kent alltt på log. Revision 1.7 2002/07/16 12:33:51 kent Kjapp gjennomgang. Fikset "supportere", "instanse-" og annet smårask.
Forrige < |
Innhold ^
|
Neste >
|