Ruby私有和公共访问器

A F*_*kly 5 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
...
Run Code Online (Sandbox Code Playgroud)

但突然间我正在使用一个原始实例变量,这让我抽搐了.我的意思是,如果我需要在未来某一日期设定之前处理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
Run Code Online (Sandbox Code Playgroud)

这完成了工作,但对于一个简单的存取器而言,这是一大块样板,而且非常坦率地说:ew.

那么有更好的,更Ruby的方式吗?

Ale*_*fee 9

private 可以带一个符号arg,所以...

class Pancake
  attr_accessor :has_sauce
  private :has_sauce=
end
Run Code Online (Sandbox Code Playgroud)

或者

class Pancake
  attr_reader :has_sauce
  attr_writer :has_sauce; private :has_sauce=
end
Run Code Online (Sandbox Code Playgroud)

等等...

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

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

  • 我已经看到(并修复了)需要为客户端对象重构以使用访问器而不是底层机制的 Ruby 代码,即使实例变量不是直接可见的。底层机制并不总是一个实例变量——它可以是对隐藏在外观后面的类的委托或操作,或具有特定格式的会话存储,或所有类型。它可以改变。如果您需要交换技术、库、对象规范等,“自封装”会有所帮助。 (2认同)

Fre*_*ung 5

attr_reader 等等只是方法 - 没有理由你可以为自己的用途定义变种(我确实分享你的情绪)例如:

class << Object
  def private_accessor(*names)
    names.each do |name|
      attr_accessor name
      private "#{name}="
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

然后private_accessor像你一样使用attr_accessor(我认为你需要一个比private_accessor更好的名字)


Dor*_*ian 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
Run Code Online (Sandbox Code Playgroud)

这将按预期引发错误:

private method `students' called for #<School:0x00007fcc56932d60 @students=[1, 2, 3]> (NoMethodError)
Run Code Online (Sandbox Code Playgroud)