Ruby私有和公共访问器

时间:2014-08-29 15:39:12

标签: ruby access-modifiers accessor attr-accessor

在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的方式吗?

4 个答案:

答案 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更多;)