Rozluštění tříd, instancí a Metaclasses v Ruby

Vítejte v nové epizodě Ruby Magic! Vydání tohoto měsíce je o metaclasses, což je téma vyvolané diskusí mezi dvěma vývojáři (Ahoj Maud!).

zkoumáním metaclasses se naučíme, jak metody třídy a instance fungují v Ruby. Po cestě Objevte rozdíl mezi definováním metody předáním explicitního „definee“ a použitím class << self nebo instance_eval. Jdeme!

Instance Třídy a Instance Metody

pochopit, proč metaclasses jsou používány v Ruby, začneme tím, že zkoumá, jaké jsou rozdíly mezi instance a metody třídy.

v Ruby je třída objekt, který definuje plán pro vytvoření dalších objektů. Třídy definují, které metody jsou k dispozici na kterékoli instanci této třídy.

definování metody uvnitř třídy vytvoří metodu instance v této třídě. Každá budoucí instance této třídy bude mít tuto metodu k dispozici.

1 2 3 4 5 6 7 8 9 10 11 12
class User def initialize(name) @name = name end def name @name end end user = User.new('Thijs') user.name # => "Thijs" 

V tomto příkladu jsme se vytvořit třídy s názvem User, s instance metodu s názvem #name, která vrací jméno uživatele. Pomocí třídy pak vytvoříme instanci třídy a uložíme ji do proměnné s názvem user. Protože user je instancí třídy User, má k dispozici metodu #name.

třída ukládá své metody instance do tabulky metod. Každá instance této třídy odkazuje na tabulku metod své třídy, aby získala přístup k metodám instance.

objekty třídy

metoda třídy je metoda, kterou lze volat přímo na třídě, aniž by bylo nutné nejprve vytvořit instanci. Metoda třídy je vytvořena prefixem jejího názvu self. při definování.

třída je sama o sobě objektem. Konstanta odkazuje na objekt třídy, takže metody třídy definované na něm lze volat odkudkoli v aplikaci.

1 2 3 4 5 6 7 8 9
class User # ... def self.all end end User.all # => 

Metody definované s self.-prefix není přidán do třídy je metoda tabulky. Místo toho jsou přidány do třídy ‚ metaclass.

Metaclasses

kromě třídy má každý objekt v Ruby skrytou metaclass. Metaklasy jsou singletony, což znamená, že patří k jedinému objektu. Pokud vytvoříte více instancí třídy, budou sdílet stejnou třídu, ale všechny budou mít samostatné metaclasses.

1 2 3 4 5 6 7 8 9
thijs, robert, tom = User.all thijs.class # => User robert.class # => User tom.class # => User thijs.singleton_class # => #<Class:#<User:0x00007fb71a9a2cb0>> robert.singleton_class # => #<Class:#<User:0x00007fb71a9a2c60>> tom.singleton_class # => #<Class:#<User:0x00007fb71a9a2c10>> 

V tomto příkladu, vidíme, že i když každý z objektů má třídu User, jejich singleton třídy mají jiný objekt Id, což znamená, že jsou samostatné objekty.

tím, že má přístup k metaclass, Ruby umožňuje přidávat metody přímo do existujících objektů. Pokud tak učiníte, nepřidáte do třídy objektu novou metodu.

1 2 3 4 5 6 7 8
robert = User.new("Robert") def robert.last_name "Beekman" end robert.last_name # => "Beekman" User.new("Tom").last_name # => NoMethodError (undefined method `last_name' for #<User:0x00007fe1cb116408>) 

V tomto příkladu, musíme přidat #last_name pro uživatele uloženy v robert proměnné. Ačkoli robert je instance User, všechny nově vytvořené instance User nebudou mít přístup k metodě #last_name, protože existuje pouze na metaclass robert.

co je vlastní?

při definování metody a předání přijímače je nová metoda přidána do metaclass přijímače namísto přidání do tabulky metod třídy.

1 2 3 4 5
tom = User.new("Tom") def tom.last_name "de Bruijn" end 

V příkladu výše, přidali jsme #last_name přímo na tom objekt, tím, že projde tom jako přijímač při definování metody.

to je také, jak to funguje pro metody třídy.

1 2 3 4 5 6 7
class User # ... def self.all end end 

Tady jsme se projít explicitně self jako přijímač při vytváření .all metoda. V definici třídy self odkazuje na třídu (v tomto případěUser), takže metoda .all je přidána do metaclass User.

Protože User je objekt uložen v konstantní, budeme přístup stejný objekt—a stejné metaclass—kdykoli jsme se na něj odkazovat.

otevření Metaclass

zjistili jsme, že metody třídy jsou metody v metaclass objektu třídy. Když to víme, podíváme se na některé další techniky vytváření třídních metod, které jste možná viděli dříve.

třída << vlastní

ačkoli se trochu vymyká stylu, některé knihovny používají class << self k definování metod třídy. Tento trik syntaxe otevírá metaclass aktuální třídy a interaguje s ním přímo.

1 2 3 4 5 6 7 8 9 10 11
class User class << self self # => #<Class:User> def all end end end User.all # => 

Tento příklad vytvoří metodu třídy s názvem User.all přidáním metodu User‚s metaclass. Namísto explicitního předání přijímače pro metodu, jak jsme viděli dříve, nastavíme self na Usermetaclass namísto User samotného.

jak jsme se dozvěděli dříve, jakákoli definice metody bez explicitního přijímače se přidá jako instanční metoda aktuální třídy. Uvnitř bloku je aktuální třída Usermetaclass (#<Class:User>).

instance_eval

další možností je použití instance_eval, které dělá totéž s jedním zásadním rozdílem. Ačkoli metaclass třídy přijímá metody definované v bloku, self zůstává odkazem na hlavní třídu.

1 2 3 4 5 6 7 8 9 10 11
class User instance_eval do self # => User def all end end end User.all # => 

V tomto příkladu definujeme metodu instance na User‚s metaclass stejně jako předtím, ale self body User. Ačkoli obvykle ukazuje na stejný objekt, „default definee“ a self mohou ukazovat na různé objekty.

Co Jsme se Naučili,

naučili Jsme se, že třídy jsou jediné objekty, které mohou mít metody, a to metody instance jsou ve skutečnosti metody na objektu metaclass. Víme, že class << self jednoduše zaměňuje self, aby vám umožnil definovat metody na metaclass, a víme, že instance_eval dělá většinou totéž (ale bez dotyku self).

ačkoli nebudete explicitně pracovat s metaclasses, Ruby je používá značně pod kapotou. Vědět, co se stane, když definujete metodu, vám pomůže pochopit, proč se Ruby chová tak, jak se chová (a proč musíte metody třídy prefixovat pomocí self.).

Díky za přečtení. Pokud se vám líbilo to, co čtete, můžete se přihlásit k odběru Ruby Magic a obdržet e-mail, když zveřejníme nový článek asi jednou za měsíc.



+