Ruby Language
Udoskonalenia
Szukaj…
Uwagi
Udoskonalenia mają zakres leksykalny, co oznacza, że obowiązują od momentu ich aktywacji (za using
słowa kluczowego using
) do momentu zmiany kontroli. Zwykle kontrola zmienia się na końcu modułu, klasy lub pliku.
Łatka na małpy o ograniczonym zasięgu
Głównym problemem łatania małp jest to, że zanieczyszcza globalny zasięg. Twój kod działa na łasce wszystkich modułów, których używasz, nie nadepną na siebie. Rubinowym rozwiązaniem tego są udoskonalenia, które są w zasadzie małpimi łatkami w ograniczonym zakresie.
module Patches
refine Fixnum do
def plus_one
self + 1
end
def plus(num)
self + num
end
def concat_one
self.to_s + '1'
end
end
end
class RefinementTest
# has access to our patches
using Patches
def initialize
puts 1.plus_one
puts 3.concat_one
end
end
# Main scope doesn't have changes
1.plus_one
# => undefined method `plus_one' for 1:Fixnum (NoMethodError)
RefinementTest.new
# => 2
# => '31'
Moduły podwójnego zastosowania (udoskonalenia lub poprawki globalne)
Dobrą praktyką jest stosowanie zakresu poprawek za pomocą udoskonaleń, ale czasem ładnie jest ładować je globalnie (na przykład podczas programowania lub testowania).
Powiedzmy na przykład, że chcesz uruchomić konsolę, potrzebujesz swojej biblioteki, a następnie masz dostępne łatki w zasięgu globalnym. Nie można tego zrobić z udoskonaleniami, ponieważ using
musi być wywołane w definicji klasy / modułu. Ale możliwe jest napisanie kodu w taki sposób, aby był on podwójny:
module Patch
def patched?; true; end
refine String do
include Patch
end
end
# globally
String.include Patch
"".patched? # => true
# refinement
class LoadPatch
using Patch
"".patched? # => true
end
Dynamiczne udoskonalenia
Udoskonalenia mają specjalne ograniczenia.
refine
może być używana tylko w zakresie modułu, ale może być programowana za pomocą send :refine
.
using
jest bardziej ograniczone. Można go wywołać tylko w definicji klasy / modułu. Mimo to może akceptować zmienną wskazującą moduł i może być wywoływana w pętli.
Przykład pokazujący te pojęcia:
module Patch
def patched?; true; end
end
Patch.send(:refine, String) { include Patch }
patch_classes = [Patch]
class Patched
patch_classes.each { |klass| using klass }
"".patched? # => true
end
Ponieważ using
jest tak statyczne, można wydać polecenie ładowania, jeśli pliki zawężania nie zostaną załadowane jako pierwsze. Sposobem na rozwiązanie tego jest zawinięcie poprawionej definicji klasy / modułu w proc. Na przykład:
module Patch
refine String do
def patched; true; end
end
end
class Foo
end
# This is a proc since methods can't contain class definitions
create_patched_class = Proc.new do
Foo.class_exec do
class Bar
using Patch
def self.patched?; ''.patched == true; end
end
end
end
create_patched_class.call
Foo::Bar.patched? # => true
Wywołanie proc tworzy łataną klasę Foo::Bar
. Może to zostać opóźnione do momentu załadowania całego kodu.