Witamy w nowym odcinku Ruby Magic! Tegoroczna edycja jest poświęcona metaklasom, tematowi wywołanemu dyskusją pomiędzy dwoma programistami (cześć Maud!).
badając metaklasy, nauczymy się jak działają metody klas I instancji w Rubim. Po drodze Odkryj różnicę między definiowaniem metody, przekazując jawną „definee” i używając class << self
lub instance_eval
. Idziemy!
instancje klas i metody instancji
aby zrozumieć, dlaczego metaklasy są używane w Rubim, zaczniemy od zbadania, jakie są różnice między instancjami a metodami klasowymi.
w Rubim klasa jest obiektem, który definiuje schemat tworzenia innych obiektów. Klasy definiują, które metody są dostępne w dowolnej instancji tej klasy.
Definiowanie metody wewnątrz klasy tworzy metodę instancji w tej klasie. Każda przyszła instancja tej klasy będzie miała dostępną metodę.
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" |
w tym przykładzie tworzymy klasę o nazwie User
, z metodą instancji o nazwie #name
, która zwraca nazwę użytkownika. Używając klasy, tworzymy instancję klasy i przechowujemy ją w zmiennej o nazwie user
. Ponieważ user
jest instancją klasy User
, dostępna jest metoda #name
.
Klasa przechowuje swoje metody instancji w swojej tabeli metod. Każda instancja tej klasy odwołuje się do tabeli metod jej klasy, aby uzyskać dostęp do jej metod instancji.
obiekty klasy
metoda klasy jest metodą, którą można wywołać bezpośrednio w klasie bez konieczności tworzenia instancji. Metoda klasy jest tworzona przez prefiksowanie jej nazwy self.
podczas jej definiowania.
klasa sama w sobie jest obiektem. Stała odnosi się do obiektu class, więc zdefiniowane na nim metody klasowe można wywoływać z dowolnego miejsca w aplikacji.
1 2 3 4 5 6 7 8 9 |
class User # ... def self.all end end User.all # => |
metody zdefiniowane z prefiksem self.
nie są dodawane do tabeli metod klasy. Zamiast tego są one dodawane do klasy ’ metaklass.
Metaklasy
oprócz klasy, każdy obiekt w Ruby ma ukryty metaklas. Metaklasy są singletonami, co oznacza, że należą do jednego obiektu. Jeśli utworzysz wiele instancji klasy, będą one współdzielić tę samą klasę, ale wszystkie będą miały oddzielne metaklasy.
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>> |
w tym przykładzie widzimy, że chociaż każdy z obiektów ma klasę User
, ich klasy singleton mają różne identyfikatory obiektów, co oznacza, że są oddzielnymi obiektami.
mając dostęp do metaklasu, Ruby umożliwia dodawanie metod bezpośrednio do istniejących obiektów. Nie spowoduje to dodania nowej metody do klasy obiektu.
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>) |
w tym przykładzie dodajemy #last_name
do użytkownika zapisanego w zmiennej robert
. Chociaż robert
jest instancją User
, wszystkie nowo utworzone instancje User
nie będą miały dostępu do metody #last_name
, ponieważ istnieje tylko na metaclassie robert
.
czym jest jaźń?
podczas definiowania metody i przekazywania odbiornika, nowa metoda jest dodawana do metaklasu odbiornika, zamiast dodawać ją do tabeli metod klasy.
1 2 3 4 5 |
tom = User.new("Tom") def tom.last_name "de Bruijn" end |
w powyższym przykładzie dodaliśmy #last_name
bezpośrednio do obiektu tom
, przekazując tom
jako odbiornik podczas definiowania metody.
tak to działa również dla metod klasowych.
1 2 3 4 5 6 7 |
class User # ... def self.all end end |
tutaj jawnie przekazujemy self
jako odbiornik podczas tworzenia metody .all
. W definicji klasy, self
odnosi się do klasy (w tym przypadkuUser
), więc metoda .all
zostanie dodana do metaklasu User
.
ponieważ User
jest obiektem przechowywanym w stałej, będziemy mieć dostęp do tego samego obiektu—i tego samego metaklasu—za każdym razem, gdy się do niego odwołamy.
otwierając Metaclass
dowiedzieliśmy się, że metody klasy są metodami w metaclass obiektu klasy. Wiedząc o tym, przyjrzymy się innym technikom tworzenia metod klasowych, które być może widzieliście wcześniej.
Klasa<< self
chociaż trochę wyszła z mody, niektóre biblioteki używają class << self
do definiowania metod klasy. Ta sztuczka ze składnią otwiera metaklasy bieżącej klasy i oddziałuje z nią bezpośrednio.
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 # => |
ten przykład tworzy metodę klasy o nazwie User.all
poprzez dodanie metody do metaklasu User
. Zamiast jawnie przekazywać odbiornik metody, jak widzieliśmy wcześniej, ustawiliśmy self
na metaklasy User
zamiast samego User
.
jak dowiedzieliśmy się wcześniej, każda definicja metody bez wyraźnego odbiornika zostanie dodana jako metoda instancji bieżącej klasy. Wewnątrz bloku, aktualną klasą jest metaclassUser
(#<Class:User>
).
instance_eval
inną opcją jest użycie instance_eval
, które robi to samo z jedną zasadniczą różnicą. Chociaż metaklass klasy otrzymuje metody zdefiniowane w bloku, self
pozostaje odniesieniem do klasy głównej.
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 # => |
w tym przykładzie definiujemy metodę instancji na metaklasie User
, tak jak poprzednio, ale self
nadal wskazuje na User
. Chociaż zwykle wskazuje na ten sam obiekt,” default definee ” i self
mogą wskazywać na różne obiekty.
czego się nauczyliśmy
dowiedzieliśmy się, że klasy są jedynymi obiektami, które mogą mieć metody, a metody instancji są w rzeczywistości metodami na metaklasie obiektu. Wiemy, że class << self
po prostu zamienia self
wokół, aby umożliwić Ci zdefiniowanie metod na metaclassie, i wiemy, że instance_eval
robi w większości to samo (ale bez dotykania self
).
chociaż nie będziesz wyraźnie pracować z metaklasami, Ruby używa ich szeroko pod maską. Wiedza o tym, co się stanie, gdy zdefiniujesz metodę, może pomóc ci zrozumieć, dlaczego Ruby zachowuje się tak, jak ona (i dlaczego musisz prefiksować metody klas za pomocą self.
).
dzięki za przeczytanie. Jeśli podoba ci się to, co czytasz, możesz zapisać się do Ruby Magic, aby otrzymywać e-mail, gdy publikujemy nowy artykuł około raz w miesiącu.