Ruby:adicionar propriedades personalizadas para classes internas
-
25-09-2019 - |
Pergunta
Pergunta: Usando o Ruby é simples adicionar personalizado métodos para as classes existentes, mas como fazer para você adicionar personalizado propriedades?Aqui está um exemplo do que estou tentando fazer:
myarray = Array.new();
myarray.concat([1,2,3]);
myarray._meta_ = Hash.new(); # obviously, this wont work
myarray._meta_['createdby'] = 'dreftymac';
myarray._meta_['lastupdate'] = '1993-12-12';
## desired result
puts myarray._meta_['createdby']; #=> 'dreftymac'
puts myarray.inspect() #=> [1,2,3]
O objetivo é construir a definição de classe de tal maneira que as coisas que não funcionam no exemplo acima irá funcionar como esperado.
Atualização: (esclarecer a pergunta), Um aspecto que foi deixado de fora da pergunta:também é um objetivo para adicionar "valores padrão" que normalmente seria no inicializar método de classe.
Atualização: (por que fazer) Normalmente, é muito simples basta criar uma classe que herda de Matriz (ou qualquer que seja incorporado na classe que você deseja emular).Esta questão deriva de alguns "testes" só de código e não é uma tentativa de ignorar isso geralmente aceitável abordagem.
Solução
Lembre-se que em Ruby, você não tem acesso aos atributos (variáveis de instância) fora da instância.Você só tem acesso a uma instância pública de métodos.
Você pode usar attr_accessor
para criar um método de uma classe que age como uma propriedade, como você descreve:
irb(main):001:0> class Array
irb(main):002:1> attr_accessor :_meta_
irb(main):003:1> end
=> nil
irb(main):004:0>
irb(main):005:0* x = [1,2,3]
=> [1, 2, 3]
irb(main):006:0> x._meta_ = Hash.new
=> {}
irb(main):007:0> x._meta_[:key] = 'value'
=> "value"
irb(main):008:0>
Uma forma simples de fazer uma inicialização padrão para um acessor, nós vamos precisar, basicamente, reimplementar attr_accessor
nós mesmos:
class Class
def attr_accessor_with_default accessor, default_value
define_method(accessor) do
name = "@#{accessor}"
instance_variable_set(name, default_value) unless instance_variable_defined?(name)
instance_variable_get(name)
end
define_method("#{accessor}=") do |val|
instance_variable_set("@#{accessor}", val)
end
end
end
class Array
attr_accessor_with_default :_meta_, {}
end
x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_
y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_
Mas espera!A saída é incorreto:
{:key=>"value"}
{:foo=>"bar", :key=>"value"}
Criamos um fechamento em torno do valor padrão de um literal de hash.
Uma maneira melhor pode ser simplesmente utilizar um bloco:
class Class
def attr_accessor_with_default accessor, &default_value_block
define_method(accessor) do
name = "@#{accessor}"
instance_variable_set(name, default_value_block.call) unless instance_variable_defined?(name)
instance_variable_get(name)
end
define_method("#{accessor}=") do |val|
instance_variable_set("@#{accessor}", val)
end
end
end
class Array
attr_accessor_with_default :_meta_ do Hash.new end
end
x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_
y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_
Agora, a saída está correta porque Hash.new
é chamado toda vez que o valor padrão é obtido, em oposição a reutilização do mesmo literal de hash de cada vez.
{:key=>"value"}
{:foo=>"bar"}
Outras dicas
Uma propriedade não é apenas um getter e um setter? Nesse caso, você não poderia fazer:
class Array
# Define the setter
def _meta_=(value)
@_meta_ = value
end
# Define the getter
def _meta_
@_meta_
end
end
Então, você pode fazer:
x = Array.new
x._meta_
# => nil
x._meta_ = {:name => 'Bob'}
x._meta_
# => {:name => 'Bob'}
Isso ajuda?