14

在 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

4 回答 4

10

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

ETC...

但是“原始”实例变量有什么问题呢?它们在您的实例内部;唯一会按名称调用它们的代码是里面的代码,pancake.rb它是你的全部。他们以 开头的事实,@我认为这让你说“blech”,这就是他们私有的原因。如果您愿意,可以将@其视为简写。private

至于处理,我认为您的直觉很好:如果可以,请在构造函数中进行处理,或者如果必须在自定义访问器中进行处理。

于 2014-08-29T15:49:57.467 回答
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 更好的名字)

于 2014-08-29T18:05:10.820 回答
5

您可以放入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)
于 2021-04-06T15:51:00.903 回答
2

直接在类中引用实例变量并没有错。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;)

于 2016-04-08T04:17:07.010 回答