Rozwikłanie klas, instancji i Metaklasów w Ruby

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 .allzostanie 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 selfnadal 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.



+