Ruby 使用具有相同实例变量名的私有方法的attr_reader

Ruby 使用具有相同实例变量名的私有方法的attr_reader,ruby,Ruby,我刚刚遇到了一个用例,其中我们有一个类,其实例变量@errors通过同名的私有方法存储 class Foo attr_reader :errors private # memoized method def errors @errors ||= [] end end f = Foo.new # private method `errors' called for #<Foo:... f.errors class-Foo 属性读取器:错误 私有的 #记忆

我刚刚遇到了一个用例,其中我们有一个类,其实例变量
@errors
通过同名的私有方法存储

class Foo
  attr_reader :errors

  private

  # memoized method
  def errors
    @errors ||= []
  end
end

f = Foo.new

# private method `errors' called for #<Foo:...
f.errors
class-Foo
属性读取器:错误
私有的
#记忆法
def错误
@错误| |=[]
结束
结束
f=Foo.new

#为#调用的私有方法“errors”如果您确实想保持reader方法的私有性,可以使用
method_missing
将调用重定向到类内的
errors
。例如:

class-Foo
属性读取器:错误
#在此处添加缺少的方法
缺少def方法(方法名称、*参数和块)
如果方法_name.to_s=='errors'
错误
其他的
超级的
结束
结束
私有的
#记忆法
def错误
@错误| |=[]
结束
结束
f=Foo.new
将f.errors.to_#s=>[]

然而,正如@SergioTulentsev在评论中所说的那样,这可能会增加代码的复杂性,特别是当类已经非常大时。

我相信,您最初的代码表达的意图最好通过以下方式来实现:不使用attr_阅读器,而是在封装的公共级别公开备忘录方法

(顺便说一句,受保护的记忆方法掩盖了attr_读取器的定义,因此代码的意图——公共非记忆访问,但私有记忆访问——实际上没有得到满足。)

考虑两种关于意图的备选方案:

  • 公共访问总是在对:errors读取器进行某些私有访问之后发生
  • 公共访问并不总是在对:errors读取器进行某些私有访问之后发生
  • 在案例1中,通过公开记忆方法并丢弃属性读取器,没有逻辑变化;这一意图仍在实现。 在案例2中,与意图相关的唯一逻辑变化是公开已记忆的方法并删除attr_读取器,即在私有访问之前的公共访问现在将记忆并返回空数组,而不是返回nil

    如果您预期的是案例2,那么您不太可能在私有访问之前依赖于公共访问期间的非备忘录,因此您可能依赖于nil响应。这是一种代码气味(根据罗伯特·马丁的干净代码):如果可能的话,最好不要通过或返回零


    相反,如果情况2是一个问题,那么最好的后续更改是使依赖于在私有访问之前为公共访问返回nil的代码,而不是依赖于在私有访问之前为公共访问返回的空数组,就像在私有访问之后为公共访问返回的空数组一样。如果这是不可能的或不可取的,请进一步澄清。

    就我个人而言,我会将私有方法重命名为
    init\u errors
    或类似的名称,因为这似乎是最简单的操作。是否有不想重命名私有方法的原因?另外,假设我们同时拥有
    attr\u reader:errors
    和名为
    errors
    的私有方法,那么类内
    Foo
    errors
    是不明确的,它可以引用私有方法或非私有方法
    errors
    作为
    attr\u reader
    仅定义类上的方法
    errors
    。不,不会有歧义。以后的方法定义只会覆盖以前的方法定义。OP代码中的
    attr\u reader
    行是完全多余的,该方法在5行之后被覆盖。只需删除
    attr\u reader
    并将记忆方法移动到公共位置。虽然这会使原来的“坏”代码正常工作,但会增加更多的复杂性和混乱。这是一个错误的解决方法。正确的方法是摆脱“我们有一个attr_阅读器,也有一个普通的阅读器,它们不会以我们想象的方式进行交互”这种奇怪的习惯。同意:method_missing,作为元编程,具有极高的认知负荷,因此,我们应该为那些能够带来巨大价值并迅速成为合法使用重型火炮的既定公约的解决方案预留空间。这里的情况并非如此,因此请避免方法_丢失。