|
|||
Forrige < |
Innhold ^
|
Neste >
|
Class
, som inneholder alle de
ting som etthvert objekt inneholder og i tillegg en liste over metoder og
en referanse til superklassen (som igjen er en annen klasse).
Alle metodekall i Ruby angir en mottaker (som per default er
self
, det gjeldene objektet).
Ruby finner metoden som skal kalles ved å se i metodelisten i mottakerens
klasse. Hvis den ikke finner metoden der, ser den i superklassen, og
deretter i superklassens superklasse og så videre.
Dersom metoden ikke er å finne i mottakerens klasse eller noen av dens
foreldre, kaller Ruby method_missing
metoden på det opprinnelige
mottakerobjektet.
Det er alt---hele forklaringen. Videre til neste kapittel.
``Men vent'', roper du, ``Jeg har brukt hardt ervervede penger på dette
kapittelet. Hva med alle disse andre greiene---singleton-klasser,
klassemetoder og så videre. Hvordan virker de?''
Det er et godt spørsmål.
lucille
, dernest objektets klasse, Guitar
,
og til slutt den klassens superklasse, Object
.
Legg merke til hvordan objektets klassereferanse (kalles klass
av historiske årsaker som virkelig går Andy på nervene) peker til klasseobjektet, og hvordan super
peker i fra den klassen til forelderklassen.
Figur 19.1: Et enkelt objekt, med dets klasse og superklasse. |
lucille.play()
, går Ruby til
mottakeren, lucille
og følger klass
referansen frem til klasseobjektet til Guitar
. Ruby søker i metodetabellen, finner play
og kjører den.
Hvis vi derimot kaller lucille.display
, begynner Ruby på samme
måte, men finner ikke display
i metodetabellen til klassen
Guitar
. Deretter følges super
referansen
frem til Guitar
sin superklasse,
Object
, hvor metoden blir funnet og kjørt.
klass
i Class
objektene ikke peker til noe meningsfylt i Figur 19.1. Vi har nå al den informasjon til å finne ut hvor de burde peke.
Når du skriver lucille.play()
, følger Ruby lucille
sin
klass
peker for å finne et klasseobjekt hvor Ruby kan søke etter
metoder. Så hva skjer når du kaller en klassemtode, slik som
Guitar.strings(...)
?
Her er mottakeren klasseobjektet i seg selv, Guitar
. Så, for å
være konsekvent, må vi putte metodene i en annen klasse, refert til fra
Guitar
sin klass
peker. Denne nye klassen
vil inneholde alle klassemetodene til Guitar
.
Den kalles for en metaklasse. Vi vil angi metaklassen til
Guitar
som Guitar$'$
.
Men det er ikke den hele og fulle sannhet. Siden
Guitar
er en subklasse av
Object
, vil dens metaklasse
Guitar$'$
være en subklasse av
Object
sin metaklasse,
Object$'$
.
I figur 19.2 viser vi disse ekstra metaklassene.
Figur 19.2: Vi legger til en metaklasse til Guitar
|
Guitar.strings()
følges samme prosessen som nevnt
tidligere: Ruby går til mottakeren, klassen Guitar
,
følger klass
referansen til klassen Guitar$'$
,
og finner metoden.
Merk til slutt at en ``S'' har dukket opp i klaggene til klassen
Guitar$'$
. Klassene som Ruby lager automatisk merkes
internt som singleton-klasser.
Singleton-klasser behandles litt anderledes inne i Ruby.
Det mest åpenbare forskjellen sett utenifra er at de er usynlige:
de vil aldri dukke opp i en liste av objekter returnert fra sånne metoder som
Module#ancestors
eller
ObjectSpace::each_object
.
String
-objekter.
Deretter assosierer vi en anonym klasse med en av dem, hvor vi
overstyrer en av metodene i basisklassen samt legger til en ny metode.
a = "hello"
|
||
b = a.dup
|
||
|
||
class <<a
|
||
def to_s
|
||
"The value is '#{self}'"
|
||
end
|
||
def twoTimes
|
||
self + self
|
||
end
|
||
end
|
||
|
||
a.to_s
|
» |
"The value is 'hello'"
|
a.twoTimes
|
» |
"hellohello"
|
b.to_s
|
» |
"hello"
|
class <<
obj'' notasjonen,
som kort og godt sier ``lag meg en ny klasse for objekt obj.''
We kunne også ha skrevet det som:
a = "hello"
|
||
b = a.dup
|
||
def a.to_s
|
||
"The value is '#{self}'"
|
||
end
|
||
def a.twoTimes
|
||
self + self
|
||
end
|
||
|
||
a.to_s
|
» |
"The value is 'hello'"
|
a.twoTimes
|
» |
"hellohello"
|
b.to_s
|
» |
"hello"
|
a
''. Dette gir oss en sterk pekepinn om hvordan Ruby implementerer
dette: en singleton klasse blir generert og satt direkte inn som a
sin klasse. Den opprinnelige klassen til a
,
String
, blir superklassen til singleton-klassen.
Før- og etter-bilder ser du i figure 19.3 på side 242(??).
Ruby gjør en liten optimalisering med disse singleton-klassene.
Hvis et objekts klass
referanse allerede peker til en
singleton-klasse, blir det ikke laget en ny. Det betyr at den første
av de to metodedefinisjonene i det forrige eksempelet vil lage en ny
singleton-klasse, men den andre definisjonen vil kun legge en metode
til i den eksisterende.
Figur 19.3: Å legge en singleton-klasse til et objekt. |
module SillyModule
|
||
def hello
|
||
"Hello."
|
||
end
|
||
end
|
||
class SillyClass
|
||
include SillyModule
|
||
end
|
||
s = SillyClass.new
|
||
s.hello
|
» |
"Hello."
|
module SillyModule
|
||
def hello
|
||
"Hi, there!"
|
||
end
|
||
end
|
||
s.hello
|
» |
"Hi, there!"
|
Figur 19.4: En inkludert modul og dens proxy klasse. |
class <<obj
'',
kan du blande en modul inn i et objekt ved hjelp av
Object#extend
.
For eksempel:
module Humor
|
||
def tickle
|
||
"hee, hee!"
|
||
end
|
||
end
|
||
|
||
a = "Grouchy"
|
||
a.extend Humor
|
||
a.tickle
|
» |
"hee, hee!"
|
extend
.
Hvis du bruker metoden i en klassedefinisjon, blir modulens metoder
klassemetoder.
module Humor
|
||
def tickle
|
||
"hee, hee!"
|
||
end
|
||
end
|
||
|
||
class Grouchy
|
||
include Humor
|
||
extend Humor
|
||
end
|
||
|
||
Grouchy.tickle
|
» |
"hee, hee!"
|
a = Grouchy.new
|
||
a.tickle
|
» |
"hee, hee!"
|
extend
tilsvarer
self.extend
, slik at metodene blir lagt til self
,
som i en klassedefinisjon er selve klassen.
class MediaPlayer include Tracing if $DEBUGGING if ::EXPORT_VERSION def decrypt(stream) raise "Decryption not available" end else def decrypt(stream) # ... end end end |
self
må referere til
et eller annet. La oss finne ut hva.
class Test puts "Type of self = #{self.type}" puts "Name of self = #{self.name}" end |
Type of self = Class Name of self = Test |
class Test def Test.sayHello puts "Hello from #{name}" end sayHello end |
Hello from Test |
Test.sayHello
,
og deretter kaller den i kroppen til klassedefinisjonen.
Fra sayHello
kaller vi name
,
en instansmetode i klassen Module
.
Da Module
er en forelder til
Class
, kan instansmetodene dens kalles uten en
eksplisitt mottaker i fra en klassedefinisjon.
Faktisk så er mange av de direktivene du bruker når du definerer en
klasse eller modul, slik som alias_method
, attr
, and
public
, ikke noe mer enn metoder i klassen
Module
. Dette åpner opp for noen interessante
muligheter---du kan utvide funksjonaliteten til klasse- og moduldefinisjoner
ved hjelp av Ruby kode. La oss se på noen eksempler.
Første eksempelet ser på hvordan vi kan legge til en enkel
dokumenteringsfasilitet til moduler og klasser. Dette vil tillate oss å
assosiere en streng med moduler og klasser som vi skriver. Denne strengen
vil være tilgjengelig mens programmet kjører. Vi velger en enkel syntaks.
class Example doc "This is a sample documentation string" # .. rest of class end |
doc
tilgjengelig for alle moduler og
klasser, så vi må gjøre den om til en instansmetode i
Module
-klassen.
class Module @@docs = Hash.new(nil) def doc(str) @@docs[self.name] = str end def Module::doc(aClass) # If we're passed a class or module, convert to string # ('<=' for classes checks for same class or subtype) aClass = aClass.name if aClass.type <= Module @@docs[aClass] || "No documentation for #{aClass}" end end class Example doc "This is a sample documentation string" # .. rest of class end module Another doc <<-edoc And this is a documentation string in a module edoc # rest of module end puts Module::doc(Example) puts Module::doc("Another") |
This is a sample documentation string And this is a documentation string in a module |
date
-modul (beskrivelse starter på side 439(??)).
La oss si at vi har en klasse som representerer en eller annen grunnleggende
kvantitet (i dette tilfellet, en dato). Klassen kan ha mange attributter
som presenterer den samme underliggende datoen på forskjellige måter:
som et Julian dagnummer, som en streng,
som en [år, måned, dag] trio av verdier og så videre.
Hver verdi representerer den samme datoen og det kan kreves en ganske
komplisert utregning for å finne verdien. Vi ønsker derfor å
bare regne ut hver attribut en gang, når den først aksesseres.
Måten å gjøre det på egen hånd, er å legge en test til hver aksessor:
class ExampleDate def initialize(dayNumber) @dayNumber = dayNumber end def asDayNumber @dayNumber end def asString unless @string # complex calculation @string = result end @string end def asYMD unless @ymd # another calculation @ymd = [ y, m, d ] end @ymd end # ... end |
once
modifikator for rutinger.
Vi vil gjerne kunne skrive noe slikt:
class ExampleDate def asDayNumber @dayNumber end def asString # complex calculation end def asYMD # another calculation [ y, m, d ] end once :asString, :asYMD end |
once
som et direktiv ved å skrive det som en
klassemeteode i ExampleDate
, men
hvordan vil det se ut internt? Trikset er å få den til å
omskrive metodene som den får navnene på. For hver metode, lager
direktivet et alias for den gamle koden og en ny metode med det samme navnet.
Den nye metoden gjør to ting. Først kaller den den opprinnelige metoden
(ved hjelp av aliaset) og lagrer den resulterende verdien i en instansvariabel.
Deretter redefinerer den seg selv, slik at senere kall ganske enkelt returnerer
instansvariabelen direkte. Her følger Tadayoshi Funaba sin kode, med noen
små omformatteringer.
def ExampleDate.once(*ids) for id in ids module_eval <<-"end_eval" alias_method :__#{id.to_i}__, #{id.inspect} def #{id.id2name}(*args, &block) def self.#{id.id2name}(*args, &block) @__#{id.to_i}__ end @__#{id.to_i}__ = __#{id.to_i}__(*args, &block) end end_eval end end |
module_eval
for å utføre en blokk med kode
i konteksten til den kallende modulen (eller, som her, den kallende klassen).
Den opprinnelige metoden får nytt navn på formen __nnn__,
hvor nnn byttes ut med den heltallige representasjonen av
metodenavnets symbolske identifikator. Koden bruker det samme navnet for
lagring (caching) av den utregnede verdien i en instansvariabel.
mesteparten av koden er en metode som dynamisk redefinerer seg selv.
Legg merke til at denne redefinsisjonen benytter seg av det faktum at
metoder kan inneholde nøstede singleton-metodedefinisjoner,
som er et lurt knep.
Forstår du denne koden er du på god vei til å bli en Ruby mester.
Men, vi kan ta det enda lengre. Titt på date
modulen og
du vil se metoden once
skrevet litt anderledes.
class Date class << self def once(*ids) # ... end end # ... end |
class << self
''.
Dette definerer en klasse basert på objektet self
,
som tilfeldigvis er klasseobjektet til Date
.
Resultatet? En hver metode inne i den indre klassedefinisjonen blir
automatisk en klassemetode i Date
.
Metoden med once
er generelt brukbar---den bør fungere
for en hver klasse. Dersom du tok once
og gjorde den om
til en privat instansmetode i Module
-klassen,
ville den være tilgjengelig for alle Ruby klasser.
Class
objektet. Når du skriver
noe slikt som String.new("gumby")
, sender du beskjeden new
til objektet som er klassen String
.
Men hvordan vet Ruby at det er det som skal gjøres? Tross alt burde mottakeren
av meldingen være en objektreferanse, hvilket impliserer at der må være en
konstant kalt ``String'' et sted som referer til String
-klasseobjektet.
[
Det vil være en konstant, ikke en variabel, da ``String'' begynner med stor bokstav.
]
Dette er faktisk det som skjer. Alle innebygde klasser, sammen med klasser du definerer,
har en tilsvarende global konstant med samme navn som klassen.
Dette er enkelt og greitt, men samtidig litt subtilt.
Subtilit fordi det er faktisk to ting vi kaller (for eksempel) String
i systemet.
Der er en konstant som referer til klasseobjektet String
,
og selve klasseobjektet.
Det faktum at klassenavn bare er konstanter betyr at du kan behandle klasser som
et hvert annet Ruby objekt: du kan kopiere dem, sende dem inn som argument til metoder
og bruke dem i uttrykk.
def factory(klass, *args)
|
||
klass.new(*args)
|
||
end
|
||
|
||
factory(String, "Hello")
|
» |
"Hello"
|
factory(Dir, ".")
|
» |
#<Dir:0x401b3234>
|
|
||
flag = true
|
||
(flag ? Array : Hash)[1, 2, 3, 4]
|
» |
[1, 2, 3, 4]
|
flag = false
|
||
(flag ? Array : Hash)[1, 2, 3, 4]
|
» |
{1=>2, 3=>4}
|
puts "Hello, World" |
"Hello, World"
genererer et String
-objekt,
så der har vi ett objekt. Vi vet også at det nakne metodekallet til puts
tilsvarer self.puts
. Men hva er ``self'' her?
self.type
|
» |
Object
|
Object
,
kan vi kalle alle metodene til Object
(deriblant de blandet inn fra
Kernel
) på funksjonsform.
Dette forklarer hvorfor vi kan kalle Kernel
metoder, slik som
puts
, på toppnivå (og for den saks skyld, over alt i Ruby):
disse metodene er en del av et hvert objekt.
class Base def aMethod puts "Got here" end private :aMethod end class Derived1 < Base public :aMethod end class Derived2 < Base end |
aMethod
på instanser av klasse Derived1
,
men ikke på instanser av Base
eller
Derived2
.
Så hvordan klarer Ruby å ha en metode med to forskjellige grader av synlighet?
Kort fortalt, ved å jukse.
Dersom en subklasse forandrer synligheten til en metode i en forelder,
setter Ruby inn en gjemt proxy metode i subklassen som kaller
den opprinnelige metoden via super
. Deretter
setter Ruby synligheten til den proxymetoden til hva en du ba om.
Det betyr at følgende kode:
class Derived1 < Base public :aMethod end |
class Derived1 < Base def aMethod(*args) super end public :aMethod end |
super
kan få tilgang til forelderens metode uansett
synlighet, så omskrivingen gjør det mulig for subklassen å overstyre
forelderens synlighetsregler. Skumle saker, eller hva?
Object#freeze
.
Et frosset objekt kan ikke endres: du kan ikke forandre
instansvariablene (direkte eller indirekte), du kan ikke
legge til singletonmetoder til det og hvis det er en
klasse eller modul, kan du ikke legge til, slette eller endre
dens metoder.
Når et objekt først er frosset, forblir det frosset: det er ikke noen
Object#thaw
-metode. Du kan sjekke om et objekt er frosset ved hjelp av
Object#frozen?
.
Hva skjer når man kopierer et frosset objekt? Det er avhengig av
metoden du bruker. Dersom du kaller objektets clone
-metode,
blir hele objektet tilstand (inklusive om objektet er frosset eller ikke)
kopiert til det nye objektet.
På den andre side, dup
pleier typisk kun
å kopiere objektets innhold---den nye kopien vil ikke arve
den frosne tilstanden.
str1 = "hello"
|
||
str1.freeze
|
» |
"hello"
|
str1.frozen?
|
» |
true
|
str2 = str1.clone
|
||
str2.frozen?
|
» |
true
|
str3 = str1.dup
|
||
str3.frozen?
|
» |
false
|
$Log: classes.xml,v $ Revision 1.11 2003/07/31 13:22:22 kent Lenker inn figurene. Revision 1.10 2002/12/02 10:51:34 kent Første kladd ferdig for "Klasser og objekter". Revision 1.9 2002/12/01 17:01:29 kent Frem til "Class Names are Constants" Revision 1.8 2002/11/30 22:38:50 kent Frem til "Mixin modules". Revision 1.7 2002/10/21 18:54:10 kent Frem til Object-Specific Classes Revision 1.6 2002/10/20 21:29:32 kent Kopierer inn LaTeX kildekode, da en bit mangler... Har sendt e-post til Dave og Andy. Revision 1.5 2002/10/20 20:18:22 kent Fikse noen figurer og ymse tekniske feil. Revision 1.4 2002/10/20 20:08:51 kent Nettopp begynt. Frem til "Your Basic, Everyday Object".
Forrige < |
Innhold ^
|
Neste >
|