在Ruby中,受保护的类方法和私有类方法之间有什么区别吗?

oxiaedzo  于 2023-06-05  发布在  Ruby
关注(0)|答案(2)|浏览(113)

我试图理解Ruby中私有类方法和受保护类方法在可访问性方面的区别。我提供了一个代码示例,我正在使用它来研究其中的差异:

class Aclass 
    #public methods below
    def self.accessprivatemethodfromwithinsameclass
        privatemethod 
    end 

    def self.accessprotectedmethodfromwithinsameclass
        protectedmethod
    end 

class << self
    private
        def privatemethod
            "I'm a private method" 
        end 
        
    protected 
        def protectedmethod
            "I'm a protected method" 
        end 
    end
end 

class ASubClass < Aclass
    class << self 
        def accessprivatemethodfromwithinsubclass
            privatemethod
        end 

        def accessprotectedmethodfromwithinsubclass
            protectedmethod
        end 

    end
end 

#Example 
Aclass.protectedmethod 
Aclass.privatemethod
Aclass.accessprivatemethodfromwithinsameclass
Aclass.accessprotectedmethodfromwithinsameclass
ASubClass.accessprivatemethodfromwithinsubclass
ASubClass.accessprotectedmethodfromwithinsubclass
ASubClass.privatemethod
ASubClass.protectedmethod

到目前为止,我的结论是:

  • 两者都可以从类ie中访问。如self.accessprivatemethodfromwitinsameclassself.accessprotectedfromwithinsameclass
  • 这两个函数都不能从类外部访问,如Aclass.protectedmethodAclass.privatemethod
  • 两者都可以从子类中访问,参见access...methodwithinsubclass
  • 但是这两个都不能在子类中直接调用。参见ASubclass.private/protectedmethod

行为上的唯一区别是,当它们不可访问时,受保护的方法会说:
NoMethodError:保护方法要求上课
其中private方法说:
NoMethodError:私有方法...要求上课
我的质询如下:是否有过行为上的差异?

utugiqy6

utugiqy61#

Ruby中没有类方法

第一个需要理解的重要概念是 * 在Ruby对象模型中没有类方法这样的东西 *。我们通俗地谈论类方法,甚至在Ruby核心库中也有方法引用类方法(例如Module#public_class_methodModule#private_class_method),但 * 根本 *,在对象模型中,它们不存在。
我们所谓的类方法实际上只是一个普通的单例方法,任何对象都可以拥有它。只不过这个对象恰好是类Class的一个示例。但从根本上说,作为Foo的示例的对象的单例方法和作为Class的示例的对象的单例方法之间没有区别。
我们为后者使用一个特殊名称的原因是它们非常常见,并且“碰巧是Class示例的对象的单例方法”是一个拗口的词,所以我们说“类方法”。

Ruby中没有singleton方法

第二个需要理解的重要概念是 * 在Ruby对象模型中也没有单例方法 *!我们通俗地谈论单例方法,甚至在Ruby核心库中也有引用单例方法的方法(例如Object#singleton_methodObject#singleton_methodsObject#define_singleton_method),但 * 从根本上 *,在对象模型中,它们并不存在。
我们所谓的单例方法实际上只是单例类的一个普通的示例方法。但它是一个标准的示例方法,没有什么特别之处。特殊的是singleton class,而不是 method。但从根本上说,在任何其他模块中定义的示例方法和在单例类中定义的单例方法之间没有区别。
我们为后者使用一个特殊名称的原因是,它有助于区分它们,而“碰巧是单例类的模块的示例方法”是一个拗口的词,所以我们说“单例方法”。
类似地,“一个模块的示例方法恰好是一个对象的单例类,而这个对象恰好是Class的示例”也是一个拗口的词,所以应该说“类方法”。

Ruby消息无障碍

然而,既然我们现在知道类方法只是单例方法,单例方法只是示例方法(事实上,Ruby中只有一种方法,示例方法,没有单例方法,没有类方法,没有静态方法,没有构造函数),我们可以简单地看看Ruby中的消息可访问性,因为我们知道类方法没有什么特别之处。
Ruby有三个消息可访问性级别:

  • public(默认值)
  • private
  • protected

Aside:在哪里定义?

不幸的是,与许多其他编程语言不同,Ruby语言规范并不作为一个文档存在于一个地方。Ruby没有一个正式的规范来定义某些语言结构的含义。
有几个资源,其中的 * 总和 * 可以被认为是Ruby编程语言的规范。
这些资源包括:

  • 注意,ISO Ruby规范是在2009-2010年左右编写的,其特定目标是当时所有现有的Ruby实现都可以轻松兼容。由于YARV和MacRuby只实现了Ruby 1.9+,MRI只实现了Ruby 1.8及更低版本,而JRuby、XRuby、Ruby.NET和IronRuby(当时)只实现了Ruby 1.8的一个子集,这意味着ISO Ruby规范只包含Ruby 1.8和Ruby 1.9共有的功能。此外,ISO Ruby规范的目的是最小化,只包含编写Ruby程序绝对需要的功能。正因为如此,例如,它只指定了非常广泛的String s(因为它们在Ruby 1.8和Ruby 1.9之间发生了显着变化)。它显然也没有指定在ISO Ruby规范编写之后添加的功能,例如Ractors或Pattern Matching。

  • The Ruby Spec Suite aka ruby/spec-请注意,ruby/spec还远远没有完成。然而,我非常喜欢它,因为它是用Ruby而不是“ISO标准”编写的,这对于Rubyist来说更容易阅读,而且它也是一个可执行的一致性测试套件。

  • The Ruby Programming Language by David Flanagan and Yukihiro 'matz' Matsumoto-这本书是由大卫Flanagan和Ruby的创造者matz共同编写的,作为Ruby的语言参考。

  • Programming Ruby by Dave Thomas, Andy Hunt, and Chad Fowler-这本书是第一本关于Ruby的英语书,并且在很长一段时间内作为Ruby的标准介绍和描述。这本书还首次记录了Ruby核心库和标准库,作者将这些文档捐赠给了社区。

  • Ruby Issue Tracking System,特别是Feature sub-tracker-然而,请注意,不幸的是,社区在区分Ruby编程语言的Ticket和YARV Ruby实现的Ticket方面非常非常糟糕:它们都被混在追踪器里

  • Ruby Developer Meetings的会议日志。(同样的问题:Ruby和YARV混合在一起。)

  • mailing lists上经常讨论新功能,特别是ruby-core (English)ruby-dev (Japanese)邮件列表。(同样的问题再次出现)。

  • Ruby documentation-再次提醒,本文档是从YARV的源代码生成的,并没有区分Ruby的特性和YARV的特性。

  • 在过去,有几次尝试对Ruby规范进行形式化更改,例如Ruby Change Request (RCR)Ruby Enhancement Proposal (REP)过程,但都没有成功。

  • 如果所有这些都失败了,您需要检查流行的Ruby实现的源代码,看看它们实际上是做什么的。请注意复数:您必须查看多个(理想情况下是所有)实现,以确定共识是什么。只看一个实现 * 不可能告诉你 * 你正在看的是这个特定实现的一个实现怪癖,还是Ruby语言的一个普遍认可的行为。

public

public很简单:它没有任何限制。好了

private

private可访问性的规则也非常简单:消息发送方只能在以下情况下调用私有方法

接收器是self是不够的!例如,这是不允许的:

foo = self
foo.some_private_method

接收者 * 必须 * 是隐式的,或者它必须是 * 文字伪变量关键字self *。
这种限制的原因是Ruby实现的实现者使用高度复杂的,积极优化的编译器希望能够 * 静态地 * 确定是否允许消息发送调用私有方法。如果你把这个规则放宽到“如果接收者是self,可以调用私有方法”,那么你就不能再静态地确定这一点了,请看下面的例子:

foo = if rand < 0.5 then self else some_other_object end
foo.some_private_method

下面是13.3.5.3ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification的www.example.com Private methods 部分的内容:

13.3.5.3私有方法

私有方法是其可见性属性设置为私有可见性的方法。
私有方法不能用显式接收器调用,即,如果 primary-expressionchained-method-invocation 出现在方法调用中与方法接收器对应的位置,则私有方法不能被调用,除非以下任何形式的方法调用,其中 primary-expressionself-expression

    • 单方法赋值 *
    • 简化方法赋值 *
    • 单索引赋值 *
    • 缩写索引赋值 *

请注意,ISO Ruby编程语言规范自2012年以来一直没有更新,因此这是一个稍微过时的定义。该规范在几年前被稍微简化了。现在,第二段可能更像这样读:
私有方法不能用显式接收器调用,即,如果 primary-expressionchained-method-invocation 出现在方法调用中对应于方法接收器的位置,则私有方法不能被调用,除非 primary-expressionself-expression 的方法调用。
但请注意,这在这里没有区别,因为在您的代码中,您无论如何都没有使用 self-expression 作为接收器,因此 self-expression 是否在有限数量的情况下被允许或总是无关紧要。
ruby/spec for privatemessage sends似乎也不是完全最新的,但同样,差异对您的问题无关紧要。
然而,Modules的RDoc,特别是关于可见性的小节,是完全最新的,并且非常清楚:
第三个可见性是私有的。私有方法只能在没有接收器的情况下从所有者类内部调用,或者使用文本self作为接收器。如果调用私有方法的接收器不是文本self,则将引发NoMethodError

protected

为了允许调用protected方法,sender 需要是定义 *invoked方法 * 的模块的示例。
换句话说,如果你在模块M中定义了一个protected方法m,如下所示:

module M
  protected def m; end
end

那么m只能由发送方是M示例的消息发送调用:

kind_of?(M) #=> true
M === self  #=> true

receiver.m

换句话说,为了

some_object.some_protected_method

为了成功,

kind_of?(some_object.method(:some_protected_method).owner)

some_object.method(:some_protected_method).owner === self

必须是true
[Note:我假设Object#methodMethod#ownerModule#===Object#kind_of?没有被猴子修补或覆盖。检查 * 不 * 保证使用那些实际的方法执行,它通常使用实现的内部知识执行。换句话说,您不能通过monkey修补或重写这些方法来重写protected的行为。
以下是13.3.5.4ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification的www.example.com Protected methods 部分的内容:

13.3.5.4受保护的方法

受保护的方法是其可见性属性设置为受保护的可见性的方法。
当且仅当以下条件成立时,才可以调用受保护的方法:

  • 假设 M 是类Module的一个示例,其中存在方法的绑定。
  • M* 包含在当前自身中,或者 M 是当前自身的类或其超类之一。

如果 M 是一个单例类,则该方法是否可以被调用可以以实现定义的方式确定。
和RDoc:
第二个可见性是protected。当调用受保护的方法时,发送方必须继承定义该方法的ClassModule。否则将引发NoMethodError
受保护的可见性最常用于定义==和其他比较方法,其中作者不希望向任何调用者公开对象的状态,并希望将其限制为仅继承的类。

您的示例

现在,让我们将上面学到的应用到您的示例中:
到目前为止,我的结论是:

  • 两者都可以从类ie中访问。如self.accessprivatemethodfromwitinsameclassself.accessprotectedfromwithinsameclass

在这里,类是无关紧要的。重要的是发送方和接收方(对于protected),或者只是接收方(对于private)。
privatemethod可以工作,不是因为它“在同一个类中”,而是因为接收者是隐式的。
protectedmethod可以工作,不是因为它“在同一个类中”,而是因为发送者和接收者都是同一个对象(发送者 * 总是 * self,在这种情况下接收者是隐式的,而隐式的接收者总是self),所以发送者是接收者类型的示例是平凡的。

  • 两者都不能像Aclass.protectedmethodAclass.privatemethod那样从类外部访问。

同样,类在这里并不重要。
Aclass.privatemethod不工作,不是因为它“在类之外”,而是因为接收者既不是隐式的(即,privatemethod)或文字伪变量关键字self(即self.privatemethod)。
Aclass.protectedmethod不工作,不是因为它“在类之外”,而是因为发送方(在本例中,未命名的顶级对象通常称为main,它是Object的直接示例)不是定义该方法的模块的示例(在本例中为Aclass.singleton_class)。

  • 两者都可以从子类中访问,参见access...methodwithinsubclass

同样,子类在这里无关紧要。事实上,这两个案例与你第一个要点中的第一个和第二个案例完全相同。
privatemethod可以工作,不是因为它“在子类中”,而是因为接收者是隐式的。
protectedmethod可以工作,不是因为它“在子类中”,而是因为发送者和接收者都是同一个对象(发送者 * 总是 * self,在这种情况下接收者是隐式的,而隐式的接收者总是self),所以发送者是接收者类型的示例是平凡的。

  • 但是这两个都不能在子类中直接调用。参见ASubclass.private/protectedmethod

同样,子类在这里并不真正相关。事实上,这两个案例与你之前第二个要点中的第三和第四个案例完全相同。
ASubclass.privatemethod不工作,不是因为它“在类之外”,而是因为接收者既不是隐式的(即,privatemethod)或文字伪变量关键字self(即self.privatemethod)。
ASubclass.protectedmethod不工作,不是因为它“在类之外”,而是因为发送方(在本例中,未命名的顶级对象通常称为main,它是Object的直接示例)不是定义该方法的模块的示例(在本例中为Aclass.singleton_class)。

一个更有趣的例子

class Superclass
  class << self
    protected def protected_method; end
  end
end

class Subclass < Superclass
  Superclass.protected_method
end

这是允许的还是不允许的?(剧透警告:答案会先让你失望,但随后又会让你满意。)
好吧,让我们看看。记住protected的规则:sender 必须是定义了 *invoked方法 * 的模块的示例。
因此,被调用的方法在其中定义的模块是Superclass.singleton_class,即对象Superclass的单例类。

  • sender* 是对象Subclass

所以问题是对象Subclass是类Superclass.singleton_class的示例吗?这个问题的答案并不明显。
显然,SubclassSuperclass的子类。但这对我们没有帮助。
而且,很明显,SubclassSuperclass都是ClassSubclass.class == ClassSuperclass.class == Class)的示例,这也意味着它们是Class的所有超类ModuleObjectKernelBasicObject的示例。但这也帮不了我们:发送方必须是模块的示例 * 被调用的方法在 * 中定义。发送方和接收方在其祖先中有多少个共同的 other 模块并不重要,它们需要特定地有共同的 this 模块(在本例中为Superclass.singleton_class

此外,根据单例类的定义,SuperclassSuperclass.singleton_class的示例,SubclassSubclass.singleton_class的示例。但是这也帮不了我们,因为我们需要的是 * Subclass * 是 * Superclass.singleton_class * 的一个示例。
所以,看起来答案是:不,这是不允许的。
当你尝试它的时候,它会起作用。但是,为什么呢?
当我引用ISO/IEC 30170:2012 Information technology — Programming languages — Ruby specification的www.example.com Protected methods 一节时,也许你已经发现了“逃生舱口”13.3.5.4:

13.3.5.4受保护的方法

受保护的方法是其可见性属性设置为受保护的可见性的方法。
当且仅当以下条件成立时,才可以调用受保护的方法:

  • M 是类Module的一个示例,其中存在该方法的绑定。
  • M* 包含在当前自身中,或者 M 是当前自身的类或其超类之一。

如果 M 是一个单例类,则该方法是否可以被调用可以以实现定义的方式确定。
特别是,请注意最后一段[***粗体斜体***强调我的]:
如果 M 是***单例类***,则可以通过***实现自定义***的方式来确定方法是否可以被调用。
所以,对于“这是否有效”这个问题,令人失望的答案是:我们不知道。Ruby没有说它是否有效。这取决于具体的实现。
从外观上看,它可能会工作,也可能不会工作,这取决于实现。
但不要害怕!
我们有ruby/spec for singleton classes来拯救我们!上面是这么写的:

describe "A singleton class" do
  it "is a subclass of a superclass's singleton class" do
    ClassSpecs::K.singleton_class.superclass.should ==
      ClassSpecs::H.singleton_class
  end
end

所以,它起作用是因为子类的单例类成为子类的单例类的超类。让我们打开它。
当我创建超类的子类时:

class Superclass; end
class Subclass < Superclass; end

然后,超类(Superclass.singleton_class)的单例类成为单例类(Subclass.singleton_class.superclass)的超类。
或者,换句话说,对于任何类CC.singleton_class.superclass == C.superclass.singleton_class。(当然,不包括没有超类的顶级类。
因为发信人(Subclass)是间接示例(Subclass是它自己的单例类Subclass.singleton_class的示例,而单例类Subclass.singleton_class又是Superclass.singleton_class的子类,因此Subclass间接地是Superclass.singleton_class的示例)(Superclass.singleton_class),根据protected可访问性规则,允许调用protected方法。
呼。
由于ruby/spec适用于所有Ruby实现(除非使用保护子句明确声明),因此可以保证所有Ruby实现的所有当前维护版本都是如此。

bbmckpt7

bbmckpt72#

这些是 * 封装控件 *。一般来说,您会希望坚持使用protected,以防止“外部”代码与内部代码混淆,但更重要的是,更好地控制公共接口,并扩展为contract
任何protected都不打算在该类的“家族”之外使用,因此子类可以访问。这是大多数代码使用的,因为它通常不关心其他子类。
private方法更加具体,也限制了对派生类的访问。
你为什么要这么做这是情境性的,但一个常见的原因是您正在编写一个库,并且您所鼓励的模式是在库中子类化一些类。为了保持 * 封装 *,其中一些方法是private
这意味着您可以在将来使用这些方法,而无需对依赖于该库的任何代码进行破坏性更改。你给它们标上了private,意思是“请勿使用,如有更改,恕不另行通知”。

1在Ruby中,你总是可以找到一种方法来访问代码,即使它是protectedprivate,但它被认为是粗鲁和/或有风险的。在其他语言中,如C++或Java,编译器通常会直接拒绝任何此类访问。在鲁比,你只是拉等级。

相关问题