在Ruby中定义访问器时,简洁(我们都喜欢)和最佳实践之间可能存在紧张关系。
例如,如果我想在实例上公开一个值但禁止任何外部对象更新它,我可以执行以下操作:
class Pancake
attr_reader :has_sauce
def initialize(toppings)
sauces = [:maple, :butterscotch]
@has_sauce = toppings.size != (toppings - sauces).size
...
但突然间我正在使用原始实例变量,这让我抽搐了。我的意思是,如果我需要在将来设置之前处理has_sauce,我可能需要进行更多的重构,而不仅仅是覆盖访问器。来吧,原始实例变量?布莱什。
我可以忽略该问题并使用attr_accessor
。我的意思是,任何人都可以设置属性,如果他们真的想要;毕竟,这就是Ruby。但后来我失去了数据封装的想法,对象的界面定义不太明确,系统可能更混乱。
另一种解决方案是在不同的访问修饰符下定义一对访问器:
class Pancake
attr_reader :has_sauce
private
attr_writer :has_sauce
public
def initialize(toppings)
sauces = [:maple, :butterscotch]
self.has_sauce = toppings.size != (toppings - sauces).size
end
end
这完成了工作,但对于一个简单的访问者而言,这是一大块样板,坦率地说:ew。
那么有更好的,更Ruby的方式吗?
答案 0 :(得分:5)
attr_reader
等只是方法 - 没有理由你可以为自己的用途定义变体(我确实分享你的观点)例如:
class << Object
def private_accessor(*names)
names.each do |name|
attr_accessor name
private "#{name}="
end
end
end
然后像private_accessor
那样使用attr_accessor
(我认为你需要一个比private_accessor更好的名字)
答案 1 :(得分:3)
private
可以采用符号arg,所以......
class Pancake
attr_accessor :has_sauce
private :has_sauce=
end
或
class Pancake
attr_reader :has_sauce
attr_writer :has_sauce; private :has_sauce=
end
等...
但是&#34; raw&#34;的问题是什么?实例变量?它们是您实例的内部;唯一会按名称调用它们的代码是pancake.rb
内的代码,这些代码都是你的。事实上,他们以@
开头,我认为这让你说出了#34; blech&#34;,这就是他们私有化的原因。如果您愿意,可以将@
视为private
的简写。
至于处理,我认为你的直觉很好:如果可以,可以在构造函数中进行处理,如果必须,可以在自定义访问器中进行处理。
答案 2 :(得分:3)
您可以将 attr_reader
放在 private
范围内,如下所示:
class School
def initialize(students)
@students = students
end
def size
students.size
end
private
attr_reader :students
end
School.new([1, 2, 3]).students
这将按预期引发错误:
private method `students' called for #<School:0x00007fcc56932d60 @students=[1, 2, 3]> (NoMethodError)
答案 3 :(得分:0)
在类中直接引用实例变量没有任何问题。 attr_accessor
无论如何都是间接地做到这一点,无论你是将这些方法公之于众还是私有方式。
在此特定示例中,可能有助于识别toppings
可能是您要为其他目的保存的属性,has_sauce
是“虚拟属性”,这是模型的特征取决于潜在的浇头属性。
这样的事情可能会让人觉得更清洁:
class Pancake
def initialize(toppings)
@toppings = toppings
end
def has_sauce?
sauces = [:maple, :butterscotch]
(@toppings & sauces).any?
end
end
由您决定是否公开attr_accessor :toppings
。如果您只是扔掉浇头,那么您的课程不再是Pancake
而是PancakeToppingDetector
更多;)