在Ruby中,假设我有一个类Foo
,允许我对我的大量Foos进行编目。所有Foos都是绿色和球形的,这是一个基本的自然法则,所以我定义了类方法如下:
class Foo
def self.colour
"green"
end
def self.is_spherical?
true
end
end
这让我做
Foo.colour # "green"
但不是
my_foo = Foo.new
my_foo.colour # Error!
尽管my_foo
显然是绿色的。
显然,我可以定义一个调用colour
的实例方法self.class.colour
,但如果我有许多这样的基本特征,那就会变得笨拙。
我也可以通过定义method_missing
为任何缺失的方法尝试类来做到这一点,但我不清楚这是我应该做的事情还是丑陋的黑客,或者如何安全地做到(特别是因为我实际上在Rails中使用ActiveRecord,我理解它使用method_missing做了一些聪明的乐趣。)
你会推荐什么?
答案 0 :(得分:23)
Ruby附带的Forwardable模块可以很好地完成这个任务:
#!/usr/bin/ruby1.8
require 'forwardable'
class Foo
extend Forwardable
def self.color
"green"
end
def_delegator self, :color
def self.is_spherical?
true
end
def_delegator self, :is_spherical?
end
p Foo.color # "green"
p Foo.is_spherical? # true
p Foo.new.color # "green"
p Foo.new.is_spherical? # true
答案 1 :(得分:9)
如果它是普通的Ruby,那么使用Forwardable是正确的答案
如果它是Rails,我会使用delegate,例如
class Foo
delegate :colour, to: :class
def self.colour
"green"
end
end
irb(main):012:0> my_foo = Foo.new
=> #<Foo:0x007f9913110d60>
irb(main):013:0> my_foo.colour
=> "green"
答案 2 :(得分:4)
您可以使用模块:
module FooProperties
def colour ; "green" ; end
def is_spherical? ; true ; end
end
class Foo
extend FooProperties
include FooProperties
end
有点难看,但比使用method_missing
更好。我会尝试将其他选项放在其他答案中......
答案 3 :(得分:4)
从设计的角度来看,我认为,尽管所有Foos
,颜色和球形的答案是相同的?是Foo
实例的属性,因此应该定义为实例方法而不是类方法。
但是我可以看到一些你想要这种行为的情况,例如:当你的系统中有Bars
时,所有这些都是蓝色的,你会在代码中的某个地方传递一个类,并想知道在你上课时调用new
之前实例的颜色
另外,ActiveRecord确实广泛使用了method_missing
,这是正确的。对于动态查找器,所以如果你沿着那条路走下去,你需要确保你的method_missing从超类中调用一个,如果它确定方法名不是它可以自己处理的那个。
答案 4 :(得分:3)
我认为最好的方法是使用the Dwemthy's array method。
我要查阅并填写详细信息,但这是骨架
编辑:是的!工作!
class Object
# class where singleton methods for an object are stored
def metaclass
class<<self;self;end
end
def metaclass_eval &block
metaclass.instance_eval &block
end
end
module Defaults
def self.included(klass, defaults = [])
klass.metaclass_eval do
define_method(:add_default) do |attr_name|
# first, define getters and setters for the instances
# i.e <class>.new.<attr_name> and <class>.new.<attr_name>=
attr_accessor attr_name
# open the class's class
metaclass_eval do
# now define our getter and setters for the class
# i.e. <class>.<attr_name> and <class>.<attr_name>=
attr_accessor attr_name
end
# add to our list of defaults
defaults << attr_name
end
define_method(:inherited) do |subclass|
# make sure any defaults added to the child are stored with the child
# not with the parent
Defaults.included( subclass, defaults.dup )
defaults.each do |attr_name|
# copy the parent's current default values
subclass.instance_variable_set "@#{attr_name}", self.send(attr_name)
end
end
end
klass.class_eval do
# define an initialize method that grabs the defaults from the class to
# set up the initial values for those attributes
define_method(:initialize) do
defaults.each do |attr_name|
instance_variable_set "@#{attr_name}", self.class.send(attr_name)
end
end
end
end
end
class Foo
include Defaults
add_default :color
# you can use the setter
# (without `self.` it would think `color` was a local variable,
# not an instance method)
self.color = "green"
add_default :is_spherical
# or the class instance variable directly
@is_spherical = true
end
Foo.color #=> "green"
foo1 = Foo.new
Foo.color = "blue"
Foo.color #=> "blue"
foo2 = Foo.new
foo1.color #=> "green"
foo2.color #=> "blue"
class Bar < Foo
add_defaults :texture
@texture = "rough"
# be sure to call the original initialize when overwriting it
alias :load_defaults :initialize
def initialize
load_defaults
@color = += " (default value)"
end
end
Bar.color #=> "blue"
Bar.texture #=> "rough"
Bar.new.color #=> "blue (default value)"
Bar.color = "red"
Bar.color #=> "red"
Foo.color #=> "blue"
答案 5 :(得分:2)
您可以定义一个直通设施:
module Passthrough
def passthrough(*methods)
methods.each do |method|
## make sure the argument is the right type.
raise ArgumentError if ! method.is_a?(Symbol)
method_str = method.to_s
self.class_eval("def #{method_str}(*args) ; self.class.#{method_str}(*args) ; end")
end
end
end
class Foo
extend Passthrough
def self::colour ; "green" ; end
def self::is_spherical? ; true ; end
passthrough :colour, :is_spherical?
end
f = Foo.new
puts(f.colour)
puts(Foo.colour)
我一般不喜欢使用eval
,但这应该是非常安全的。
答案 6 :(得分:2)
你也可以这样做:
def self.color your_args; your_expression end
define_method :color, &method(:color)
答案 7 :(得分:1)
这听起来像是一个警察,但实际上很少需要这样做,当你可以轻松地调用Foo.color。例外情况是如果您有许多定义了颜色方法的类。 @var可能是几个类中的一个,你想要显示颜色。
在这种情况下,我会问你自己在哪个地方使用这个方法 - 在课堂上,还是在模特身上?它几乎总是一个或另一个,并且使它成为一个实例方法没有任何问题,即使它在所有实例中都是相同的。
在极少数情况下,您希望两者都使用“callable”方法,您可以执行@ var.class.color(不创建特殊方法)或创建一个特殊方法,如下所示:
def color self.class.color 端
我绝对会避免使用catch-all(method_missing)解决方案,因为它可以帮助您真正考虑每种方法的用法,以及它是属于类还是实例级别。