http://www.php.net/manual/en/migration5.oop.php
В PHP 5 существует новая Объектная модель. Обработка PHP объектов была полностью переписана, допуская лучшую производительность и больше функций. В предыдущих версиях PHP объекты были обработаны как типы примитивов (например, целые числа и строки). Недостаток этого метода состоял в том, что семантически целый объект был скопирован, когда переменная была присвоена или передана в качестве параметра методу. В новом подходе на объекты ссылается дескриптор, а не значением (можно думать о дескрипторе как об идентификаторе объекта).
Это то, что я использую (я называю эту идиому hash-init).
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| send("#{k}=", v) }
end
Если вы используете Ruby 1.9, вы можете сделать это еще чище (send разрешает частные методы):
def initialize(object_attribute_hash = {})
object_attribute_hash.map { |(k, v)| public_send("#{k}=", v) }
end
Это вызовет ошибку NoMethodError, если вы попытаетесь назначить foo, а метод «foo =» не существует. Если вы хотите сделать это чисто (назначить атрибуты для каких писателей), вам следует выполнить проверку
def initialize(object_attribute_hash = {})
object_attribute_hash.map do |(k, v)|
writer_m = "#{k}="
send(writer_m, v) if respond_to?(writer_m) }
end
end
, однако это может привести к ситуациям, когда вы вводите объекту неправильные ключи (скажем, из формы), и вместо того, чтобы громко терпеть неудачу, он просто проглотить их - впереди мучительная отладка. Так что в моей книге NoMethodError - лучший вариант (это означает нарушение контракта).
Если вам просто нужен список всех писателей (нет возможности сделать это для читателей), вы делаете
some_object.methods.grep(/\w=$/)
, что означает «получить массив имен методов и grep для записей, которые заканчиваются одним знаком равенства после символа слова ".
You could override attr_reader, attr_writer and attr_accessor to provide some kind of tracking mechanism for your class so you can have better reflection capability such as this.
For example:
class Class
alias_method :attr_reader_without_tracking, :attr_reader
def attr_reader(*names)
attr_readers.concat(names)
attr_reader_without_tracking(*names)
end
def attr_readers
@attr_readers ||= [ ]
end
alias_method :attr_writer_without_tracking, :attr_writer
def attr_writer(*names)
attr_writers.concat(names)
attr_writer_without_tracking(*names)
end
def attr_writers
@attr_writers ||= [ ]
end
alias_method :attr_accessor_without_tracking, :attr_accessor
def attr_accessor(*names)
attr_readers.concat(names)
attr_writers.concat(names)
attr_accessor_without_tracking(*names)
end
end
These can be demonstrated fairly simply:
class Foo
attr_reader :foo, :bar
attr_writer :baz
attr_accessor :foobar
end
puts "Readers: " + Foo.attr_readers.join(', ')
# => Readers: foo, bar, foobar
puts "Writers: " + Foo.attr_writers.join(', ')
# => Writers: baz, foobar
Попробуйте что-нибудь вроде этого:
class Test
attr_accessor :foo, :bar
def initialize(opts = {})
opts.each do |opt, val|
send("#{opt}=", val) if respond_to? "#{opt}="
end
end
end
test = Test.new(:foo => "a", :bar => "b", :baz => "c")
p test.foo # => nil
p test.bar # => nil
p test.baz # => undefined method `baz' for #<Test:0x1001729f0 @bar="b", @foo="a"> (NoMethodError)
Это в основном то, что делает Rails, когда вы передаете хеш params в new. Он проигнорирует все параметры, о которых он не знает, и позволит вам установить вещи, которые не обязательно определены attr_accessor, но все же имеют соответствующий установщик.
Единственным недостатком является то, что для этого действительно требуется, чтобы у вас был определен установщик (а не только аксессор), который может быть не тем, что вы ищете.
Accessors are just ordinary methods that happen to access some piece of data. Here's code that will do roughly what you want. It checks if there's a method named for the hash key and sets an accompanying instance variable if so:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("@#{opt}", val.to_s) if respond_to? opt
end
end
Note that this will get tripped up if a key has the same name as a method but that method isn't a simple instance variable access (e.g., {:object_id => 42}
). But not all accessors will necessarily be defined by attr_accessor
either, so there's not really a better way to tell. I also changed it to use instance_variable_set
, which is so much more efficient and secure it's ridiculous.
Нет встроенного способа получить такой список. Функции attr _ *
по сути просто добавляют методы, создают переменную экземпляра и ничего больше. Вы можете написать для них обертки, чтобы они делали то, что хотите, но это может быть излишним. В зависимости от ваших конкретных обстоятельств вы можете использовать Object # instance_variable_defined?
и Module # public_method_defined?
.
Также избегайте использования eval, когда это возможно:
def initialize(opts)
opts.delete_if{|opt,val| not the_list_of_readers.include?(opt)}.each do |opt,val|
instance_variable_set "@#{opt}", val
end
end
You can look to see what methods are defined (with Object#methods
), and from those identify the setters (the last character of those is =
), but there's no 100% sure way to know that those methods weren't implemented in a non-obvious way that involves different instance variables.
Nevertheless Foo.new.methods.grep(/=$/)
will give you a printable list of property setters. Or, since you have a hash already, you can try:
def initialize(opts)
opts.each do |opt,val|
instance_variable_set("@#{opt}", val.to_s) if respond_to? "#{opt}="
end
end