Ruby 构造函数中的实例\变量\集
我制作了一个这样的构造函数:Ruby 构造函数中的实例\变量\集,ruby,Ruby,我制作了一个这样的构造函数: class Foo def initialize(p1, p2, opts={}) #...Initialize p1 and p2 opts.each do |k, v| instance_variable_set("@#{k}", v) end end end 我想知道像这样动态设置实例变量是否是一种好的做法,或者我是否应该像大多数LIB那样一个一个地手动设置它们,以及为什么。这个问题的答案总是基于某人的个人意见,所
class Foo
def initialize(p1, p2, opts={})
#...Initialize p1 and p2
opts.each do |k, v|
instance_variable_set("@#{k}", v)
end
end
end
我想知道像这样动态设置实例变量是否是一种好的做法,或者我是否应该像大多数LIB那样一个一个地手动设置它们,以及为什么。这个问题的答案总是基于某人的个人意见,所以这里是我的 简洁明了 如果你不能提前知道一组选项,那么你就别无选择,只能照你所做的去做。然而,如果选项是从已知集合中提取的,那么我会倾向于清晰而不是简洁,并且有明确的方法来设置选项。这也是添加任何rdoc等的好地方 安全
从安全角度来看,拥有处理选项设置的方法将允许您根据需要执行验证。当您需要执行此类操作时,参数清单会有所不同。在这种情况下,Ruby(以及大多数现代语言)中已经有了一个方便的结构:数组和哈希。在这种情况下,您应该将整个选项保存为一个散列。这将使事情变得更简单。诊断问题 您在这里所做的是一个相当简单的元编程示例,即基于某些输入动态生成代码。元编程通常会减少需要编写的代码量,但会使代码更难理解 在这种特殊情况下,它还引入了一些耦合问题:类的公共接口与内部状态直接相关,这使得在不更改另一个状态的情况下很难更改其中一个 重构示例 考虑一个稍长的示例,其中我们使用一个实例变量:
class Foo
def initialize(opts={})
opts.each do |k, v|
instance_variable_set("@#{k}", v)
end
end
def greet(name)
greeting = @greeting || "Hello"
puts "#{greeting}, name"
end
end
Foo.new(greeting: "Hi").greet
在这种情况下,如果有人想将@greeting
实例变量重命名为其他变量,他们可能很难理解如何进行重命名。很明显,@greeting
方法使用了@greeting
,但是搜索@greeting
的代码并不能帮助他们找到它的第一个设置位置。更糟糕的是,为了改变这一点内部状态,他们还必须改变对Foo.new
的任何调用,因为我们所采取的方法将内部状态与公共接口联系起来
删除元编程
让我们看看另一种选择,我们只存储所有的选项
,并将它们视为状态:
class Foo
def initialize(opts={})
@opts = opts
end
def greet(name)
greeting = @opts.fetch(:greeting, "Hello")
puts "#{greeting}, name"
end
end
Foo.new(greeting: "Hi").greet
通过删除元编程,这稍微澄清了情况。第一次希望更改此代码的新团队成员将有一段稍微轻松的时间,因为他们可以使用编辑器功能(如查找和替换)重命名内部IVAR,并且传递给初始值设定器的参数与内部状态之间的关系更加明确
减少耦合
我们可以更进一步,将内部构件与接口分离:
class Foo
def initialize(opts={})
@greeting = opts.fetch(:greeting, "Hello")
end
def greet(name)
puts "#{@greeting}, name"
end
end
Foo.new(greeting: "Hi").greet
在我看来,这是我们研究过的最好的实现:
grep
,git log-s
,等等opts.fetch
,我们向我们类的未来读者明确了opts
参数应该是什么样子,而不是让他们阅读整个类即使是在库代码中,我也希望能够清晰地重复几行。您可以使用
attr\u accessor
声明可用的实例变量,并动态调用setter:
class Foo
attr_accessor :bar, :baz, :qux
def initialize(opts = {})
opts.each do |k, v|
public_send("#{k}=", v)
end
end
end
Foo.new(bar: 1, baz: 2) #=> #<Foo:0x007fa8250a31e0 @bar=1, @baz=2>
Foo.new(qux: 3) #=> #<Foo:0x007facbc06ed50 @qux=3>
class-Foo
属性存取器:bar,:baz,:qux
def初始化(opts={})
选择每个do | k,v|
公共发送(“#{k}=”,v)
结束
结束
结束
Foo.new(酒吧:1,酒吧:2)#=>#
Foo.new(qux:3)#=>#
如果传递了未知选项,此方法也会显示错误:
Foo.new(quux: 4) #=> undefined method `quux=' for #<Foo:0x007fd71483aa20> (NoMethodError)
Foo.new(qux:4)#=>for#(NoMethodError)的未定义方法'qux='
很有意义,我现在倾向于明确设置选项。我将等待其他观点,然后再将其设置为已解决。谢谢稍后您将如何处理所有这些实例变量?基本上,它们将影响我的对象行为,具体取决于它们是否已定义以及它们的值是什么。我不知道它是否真的回答了您的问题:)您的意思是将我的选项哈希保存为实例变量?这对我来说并不简单,我想我最终会得到一个不可读的详细代码,比如@opts[:my_opt],而不是@my_opt?有什么问题吗@opts[:my_opt]
?如果这看起来太长,你可以把名字改成一个字母:@h[:my_opt]
@Logar它更像是@opts[:my_opt]
与实例变量(:my_opt)
,也就是说,你不能使用@my_opt
,因为它事先不知道,是吗?这是预先知道的,这样我倾向于同意@Chris answer并明确设置它们。是的,如果我不知道选项集,我同意保存整个散列是一个可接受的解决方案,但是如果我知道,它仍然会让我无法调用散列,而不是在初始化时定义变量。当我可以在初始值设定项中用几行调用变量时,我看不到调用哈希的意义。考虑到Sawa的经验和我的经验,我一定错了,但我无法理解why@Logar这取决于特定的用例。因为我不知道你的代码的全貌,也许你能告诉我更好的答案。最好的答案对我有帮助