Spy
, който е по същество едно прокси
Spy#calls
Spy::Error
(наследник на NoMethodError
), ако target-а не отговаря на даден методBasicObject
(даже имаше подсказка за това във форумите)
respond_to?
и public_send
BasicObject
::
пред външни константи в проксито
::NoMethodError
, ::Kernel
и т.н.
Spy.ancestors
не съдържа Object
и Spy
е клас
Spy
raise
, е усетил, че това не работи
raise
идва от Kernel
BasicObject
, не получавате нищо от Kernel
raise
, тогава?
Object.new.send :raise, ...
Kernel
се миксира (include-ва) в Object
private
Представете си нещо такова:
module Kernel
private
def puts() ... end
def raise() ... end
...
end
Kernel.raise
Kernel
прави extends self
module Kernel
extends self
private
def puts() ... end
def raise() ... end
...
end
Не ползвате attr_*
:
def calls
@called_methods
end
Ползвате attr_accessor
:
class Spy
attr_accessor :calls
end
Ползвате attr_accessor
за неща, които не трябват:
class Spy
attr_accessor :instance, :calls
end
Собствените методи на едно прокси трябва да бъдат свеждани до минимум.
Това @instance.methods.include? name
го видях на поне три места:
def method_missing(name, *args, &block)
if not @instance.methods.include? name
raise Error.new
else
@called_metods = @called_methods << name
@instance.method(name).call *args, &block
end
end
Няколко други проблема тук:
if not
- по-добре unless
или обръщане на клаузите
method_missing
foo = foo << bar
class Spy::Error < NoMethodError
end
::NoMethodError
, за да работи в случаяclass Spy
# the ultimate spy
instance_methods.each do |method|
undef_method method unless method.to_s.start_with? '__'
end
end
И това също:
class Spy
[:!, :!=, :==, :equal?].each { |method| undef_method method }
end
Това е очевидно и е проблем за проксито.
Долното не е честа практика, излишно е и е по-добре на два реда:
def initialize(object)
@object, @calls = object, []
end
#!/usr/local/bin/ruby -w
class Spy ...
Не го правете.
class Spy
attr_accessor :victim, :method_calls
...
end
Не.
class Spy
def method_missing(name, *args)
if @object.class.instance_methods(false).include? name then
...
end
end
respond_to? :wat?!
if
... then
def method_missing(name, *args, &block)
unless @object.respond_to? name
raise Error, "Undefined method #{name} called on object of class #{@object.class}."
end
@calls << name unless name == :inspect
@object.send(name, *args, &block)
end
@calls << name unless name == :inspect
- защо?
Не ползвате най-простото работещо решение:
def method_missing(name, *args, &block)
begin
result = @class_instance.public_send(name, *args, &block)
rescue NoMethodError
raise Error
else
@calls << name.to_sym
result
end
end
to_sym
е излишно
result
тук трябва да е сигналТова с Error.error
е любопитен начин за заобикаляне на липсата на raise
в контекста на Spy
, признавам:
class Spy < BasicObject
def method_missing(name, *args, &block)
if @object.respond_to? name
@calls << name
@object.method(name).call *args, &block
else
Error.error
end
end
class Error < ::NoMethodError
def self.error
raise Error
end
end
end
Само че foo.public_send :bar
, а не foo.method(:bar).call
.
Между другото, това е интересен трик, който не сме споменавали:
class Spy
def respond_to_missing?(method, *)
# I don't care about all other args except "method"
end
end
class Spy < BasicObject
class Error < ::NoMethodError; end
attr_reader :calls
def initialize(target)
@target = target
@calls = []
end
def method_missing(method, *args, &block)
if @target.respond_to? method
@calls << method
@target.public_send method, *args, &block
else
::Kernel.raise Error
end
end
end
Как Ruby пази полета и методи?
Кажете ми всичко, което знаете за instance променливите.
attr_accessor
)instance_variable_get
и компанияКой е класът на "foo"
? На Integer
? На Class
?
Кой е родителят на String
? На Object
? На Class
?
"foo".class == String # true
Integer.class == Class # true
Class.class == Class # true
String < Object # true
Object < BasicObject # true
Class < Module # true
Какво прави instance_eval
?
Изпълнява блока с променен self
.
Как Ruby знае къде да постави метод, дефиниран с def
?
Винаги има текущ клас, в който този метод отива. Той може да се промени
с module
, class
и class_eval
. Всъщност,
дори с instance_eval
, но за това — по-късно.
Може да (пре)дефинирате метод в конкретен обект.
things = [22, :f, 'Sofia']
def things.size
-5
end
def things.asl
"#{self[0]}/#{self[1]}/#{self[2]}"
end
things # [22, :f, "Sofia"]
things.size # -5
things.length # 3
things.asl # "22/f/Sofia"
[].asl # error: NoMethodError
[].size # 0
Собственият клас е достъпен чрез #singleton_class
things = []
def things.answer
42
end
things.singleton_class # #<Class:#<Array:0x4151d448>>
things.singleton_class.instance_methods(false) # [:answer]
[].singleton_class.instance_methods(false) # []
Целите числа и символите нямат собствени класове. Това е заради оптимизация. В Ruby интерпретатора, те се представят по много различен начин от всички други обекти.
1_000.singleton_class # error: TypeError
:blah.singleton_class # error: TypeError
Можете да отворите собствения клас на обект с class <<
things = [22, :f, 'Sofia']
class << things
def size
-5
end
def asl
"#{self[0]}/#{self[1]}/#{self[2]}"
end
end
things.asl # "22/f/Sofia"
things.size # -5
Оригиналният метод е достъпен чрез super
.
things = [22, :f, 'Sofia']
class << things
def each
super
yield :P
end
end
aggregated = []
for thing in things
aggregated << thing
end
aggregated # [22, :f, "Sofia", :P]
Някой има ли идея защо super
работи?
things = []
def things.answer
42
end
things.singleton_class # #<Class:#<Array:0x412261cc>>
things.singleton_class.superclass # Array
Да, eigenclass-ът е наследник на класа на обекта
Вероятно помните, че класови методи могат да се дефинират така:
class Something
def Something.foo() 42 end
def self.bar() 42 end
class << self
def qux() 42 end
end
end
Класовите методи се пазят в собствения клас на класа
class Something
def self.answer() 42 end
end
Something.singleton_class # #<Class:Something>
Something.singleton_class.instance_methods(false) # [:answer]
Помните ли extend
?
module Knowledge
def answer() 42 end
end
class Something
extend Knowledge
end
Something.answer # 42
module Knowledge
def answer() 42 end
end
text = "fourty-two"
text.extend Knowledge
text.answer # 42
Сещате ли се как може да се имплементира?
module Knowledge
def answer() 42 end
end
class Something; end
class << Something
include Knowledge
end
Something.answer # 42
module Knowledge
def answer() 42 end
end
class Something; end
Something.singleton_class.instance_eval { include Knowledge }
Something.answer # 42
module Knowledge
def answer() 42 end
end
class Something; end
Something.singleton_class.include Knowledge
Something.answer # 42
Изводът е, че include
и extend
са просто методи, викащи се на определени обекти.
Класовите методи на родителя са достъпни в класовите методи на наследника:
class Something
def self.answer() 42 end
end
class Other < Something
def self.better_answer() answer * 2 end
end
Other.better_answer # 84
Собственият клас на наследника наследява собствения клас на родителя:
class Something; end
class Other < Something; end
Something.singleton_class # #<Class:Something>
Other.singleton_class.superclass # #<Class:Something>
Something.singleton_class == Other.singleton_class.superclass # true
BasicObject
и неговия метаклас
BasicObject
наследява от Class
Метакласът на суперкласа е суперкласът на метакласа.
BasicObject
∎
object
, Ruby го търси в object.singleton_class.ancestors
Относно търсенето на (инстанционни) методи на обектите от тип String
:
''.singleton_class.ancestors
# => [#<Class:#<String:0x007f8788e51bd8>>, String,
# Comparable, Object, Kernel, BasicObject]
''.class.ancestors
# => [String, Comparable, Object, Kernel, BasicObject]
String.ancestors
# => [String, Comparable, Object, Kernel, BasicObject]
По отношение на "класовите" методи, викани върхy String
:
String.singleton_class.ancestors
# => [#<Class:String>, #<Class:Object>, #<Class:BasicObject>,
# Class, Module, Object, Kernel, BasicObject]
String.class.ancestors
# => [Class, Module, Object, Kernel, BasicObject]
Class.ancestors
# => [Class, Module, Object, Kernel, BasicObject]