extend self in ruby

Następujący kod został przetestowany z ruby 1.9.3 .

Klasa jest przeznaczona zarówno dla danych jak i zachowania

Patrzmy na ten kod ruby.

1class Util2 def self.double(i)3 i*24 end5end67Util.double(4) #=> 8

Mamy tu klasę Util. Ale zauważ, że wszystkie metody w tej klasie są metodami klasowymi. Ta klasa nie posiada żadnych zmiennych instancji. Zazwyczaj klasa jest używana do przenoszenia zarówno danych jak i zachowania, a w tym przypadku klasa Util posiada tylko zachowanie i żadnych danych.

Podobne narzędzia w ruby

Aby spojrzeć na tę dyskusję spójrzmy na kilka metod ruby, które robią podobne rzeczy. Oto kilka z nich.

1require 'base64'2Base64.encode64('hello world') #=> "aGVsbG8gd29ybGQ=\n "34require 'benchmark'5Benchmark.measure { 10*2000 }67require 'fileutils'8FileUtils.chmod 0644, 'test.rb'910Math.sqrt(4) #=> 2

We wszystkich powyższych przypadkach metoda klasy jest wywoływana bez uprzedniego tworzenia instancji. Jest to więc podobne do sposobu, w jaki używałem Util.double .

Sprawdźmy jednak, jaka jest klasa wszystkich tych obiektów.

1Base64.class #=> Module2Benchmark.class #=> Module3FileUtils.class #=> Module4Math.class #=> Module

Nie są to więc klasy, lecz moduły. Nasuwa się pytanie dlaczego mądrzy ludzie z ruby-core zaimplementowali je jako moduły zamiast tworzyć klasy w taki sposób jak ja to zrobiłem dla Util.

Powód jest taki, że klasa jest zbyt ciężka do tworzenia tylko metod jak double. Jak rozmawialiśmy wcześniej, klasa ma mieć zarówno dane jak i zachowanie. Jeśli jedyną rzeczą na której Ci zależy jest zachowanie, ruby sugeruje zaimplementować je jako moduł.

extend self is the answer

Zanim przejdę do omawiania extend self oto jak moja klasa Util będzie wyglądać po przejściu z Class do Module.

1module Util2 extend self34 def double(i)5 i * 26 end7end89puts Util.double(4) #=> 8

Więc jak działa extend self

Najpierw zobaczmy co robi extend.

1module M2 def double(i)3 i * 24 end5end67class Calculator8 extend M9end10puts Calculator.double(4)

W powyższym przypadku Calculator rozszerza moduł M i stąd wszystkie metody instancji modułu M są bezpośrednio dostępne dla Calculator.

W tym przypadku Calculator jest klasą, która rozszerzyła moduł M. Jednak Calculator nie musi być klasą, aby rozszerzyć moduł.

Teraz spróbujmy wariantu, w którym Calculator jest modułem.

1module M2 def double(i)3 i * 24 end5end567module Calculator8 extend M9end10puts Calculator.double(4) #=> 8

Tutaj Calculator jest modułem, który rozszerza inny moduł.

Teraz, gdy rozumiemy, że moduł może rozszerzyć inny moduł, spójrzmy na powyższy kod i zadajmy sobie pytanie, dlaczego moduł M jest w ogóle potrzebny. Dlaczego nie możemy przenieść metody double bezpośrednio do modułu Calculator. Spróbujmy tego.

1moduł Kalkulator2 extend Kalkulator34 def double(i)5 i * 26 end7end8puts Kalkulator.double(4) #=> 8

Pozbyłem się modułu M i przeniosłem metodę double wewnątrz modułu Calculator. Ponieważ moduł M zniknął zmieniłem metodę extend M na extend Calculator.

Jedna ostatnia poprawka.

Wewnątrz modułu Calculator czym jest self. self to sam moduł Calculator. Więc nie ma potrzeby powtarzać Calculator dwa razy. Oto ostateczna wersja

1module Calculator2 extend self34 def double(i)5 i * 26 end7end8puts Calculator.double(4) #=> 8

Konwersja klasy do modułu

Za każdym razem, gdy napotykam kod taki jak extend self, mój mózg zatrzymuje się na chwilę. Potem będę go szukał w google. Przeczytam o tym. Trzy miesiące później powtórzę cały proces.

Najlepszym sposobem, aby się tego nauczyć, jest użycie tego. Więc zacząłem szukać przypadku, aby użyć extend self. Nie jest dobrą praktyką polowanie na kod, aby zastosować pomysł, który masz w głowie, ale tutaj próbowałem się nauczyć.

Tutaj jest przed migawką metod z klasy Util, które wykorzystałem w projekcie.

1class Util2 def self.config2hash(file); end3 def self.in_cents(amount); end4 def self.localhost2public_url(url, protocol); end5end

Po użyciu extend self kod stał się

1module Util2 extend self34 def config2hash(file); end5 def in_cents(amount); end6 def localhost2public_url(url, protocol); end7end

Dużo lepszy. Sprawia, że intencje są jasne i, jak sądzę, jest to zgodne z tym, czego Ruby oczekuje od nas.

Inne użycie zgodne z tym, jak Railsy używają extend self

Buduję aplikację ecommerce i każde nowe zamówienie musi otrzymać nowy numer zamówienia z zewnętrznej aplikacji sprzedaży. Kod może wyglądać tak. Pominąłem implementację metod, ponieważ nie są one istotne dla tej dyskusji.

1class Order2 def amount; end3 def buyer; end4 def shipped_at; end5 def number6 @number || self.class.next_order_number7 end89 def self.next_order_number; 'A100'; end10end1112puts Order.new.number #=> A100

Tutaj metoda next_order_number może wykonywać skomplikowane połączenie do innego systemu sprzedaży. Idealnie byłoby, gdyby klasa Order nie eksponowała metody next_order_number . Możemy więc uczynić tę metodę private, ale to nie rozwiązuje głównego problemu. Problem polega na tym, że model Order nie powinien wiedzieć w jaki sposób generowany jest nowy numer zamówienia. Cóż, możemy przenieść metodę next_order_number do innej klasy Util, ale to stworzyłoby zbyt duży dystans.

Oto rozwiązanie wykorzystujące extend self.

1module Checkout2 extend self34 def next_order_number; 'A100'; end56 class Order7 def amount; end8 def buyer; end9 def shipped_at; end10 def number11 @number || Checkout.next_order_number12 end13 end14end1516puts Checkout::Order.new.number #=> A100

Dużo lepiej. Klasa Order nie eksponuje metody next_order_number, a ta metoda jest tam w tym samym pliku. Nie trzeba otwierać Util klasy.

Articles

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.