Ruby中的私有模块方法

120

我有一个由两部分组成的问题。

最佳实践

  • 我有一个算法,使用公共接口在数据结构上执行某些操作
  • 它目前是一个具有众多静态方法的模块,除了一个公共接口方法外,所有方法都是私有的。
  • 有一个实例变量需要在所有方法之间共享。

这些是我能看到的选项,哪个是最好的?:

  • 具有静态方法的模块 ('module' in ruby)
  • 具有静态方法的类
  • Mixin 模块用于包含到数据结构中
  • 重构 算法修改该数据结构的部分(非常小),并将其制作为混合并调用算法模块的静态方法

技术部分

有没有办法制作一个私有模块方法

module Thing
  def self.pub; puts "Public method"; end
  private
  def self.priv; puts "Private method"; end
end

这里的private似乎没有任何效果,我仍然可以毫无问题地调用Thing.priv


5
FYI,Ruby 中没有所谓的“静态”方法,它们被称为类实例方法。 - brad
37
一个老评论,但由于它有四个赞,我必须指出,“类实例方法”这种说法是不存在的。正确的术语是“类方法”。 - micapam
5
private 只影响实例方法,不影响类方法。应该使用 private_class_methodmodule Thing; def self.pub; end; private_class_method :pub; end - apeiros
3
Ruby中确实存在类实例方法,它们与类方法不同。 - Marnen Laibow-Koser
2
@micapam,有人会争辩说也没有所谓的“类方法”,“类方法”只是“单例类实例方法”的俚语。 - Cary Swoveland
10个回答

101

我认为最好的方法(大多数现有库都是这样编写的)是在模块中创建一个处理所有逻辑的类,而模块只提供一个方便的方法,例如:

module GTranslate
  class Translator
    def perform(text)
      translate(text)
    end

    private

    def translate(text)
      # do some private stuff here
    end
  end

  def self.translate(text)
    t = Translator.new
    t.perform(text)
  end
end

14
这里是一个 Ruby 新手。在这个例子中,翻译器类(Translator class)是否作为模块的公共接口暴露出来?'perform' 方法能否被限制只能由 GTranslate 访问? - rshepherd
2
@rshepherd 这里不应该是私有方法的 perform,私有方法是在 Translator 类中的私有方法(@ucron 的示例没有任何私有方法,这非常不幸)。GTranslate.translate 只是 GTranslate::Translator#perform 的一个方便方法,如果可能的话,没有真正的收益来隐藏它。 - michelpm
31
我不确定在这里开设一个类能够实现什么目的。如果目标是拥有一个私有模块方法,那么这并不符合目标。因为你可以通过调用 GTranslate::Translator.new.perform 从模块外部访问“perform”方法。换句话说,它不是私有的。 - Zack Xu
1
@jschorr 我认为Op和这个答案的意图是创建一个私有类或模块方法,而不是实例方法。此外,这并不会使任何实例方法变为私有方法,因为self.translate声明了一个类/模块方法。 - konsolebox
7
GTranslate::Translator.new.perform(text) 非常复杂,但不够私密! - abhillman
显示剩余4条评论

91

还有Module.private_class_method,这可能表达了更多的意图。

module Foo
  def self.included(base)
    base.instance_eval do
      def method_name
        # ...
      end
      private_class_method :method_name
    end
  end
end

针对问题中的代码:

module Thing
  def self.pub; puts "Public method"; end
  def self.priv; puts "Private method"; end
  private_class_method :priv
end

Ruby 2.1或更高版本:

module Thing
  def self.pub; puts "Public method"; end
  private_class_method def self.priv; puts "Private method"; end
end

我不知道这个。它是否也可以在方法定义之前使用,就像“private”一样? - Marnen Laibow-Koser
7
这个答案连同 @JCooper的回答 是真正的解决方案。@MarnenLaibow-Koser,它并不需要。你可以考虑其他答案,但要承担更多的分组和缩进成本。对于一些人来说,这可能实际上是首选解决方案。(只是为了参考而回复。) - konsolebox

78
module Writer
  class << self
    def output(s)
      puts upcase(s)
    end

    private

    def upcase(s)
      s.upcase
    end
  end
end

Writer.output "Hello World"
# -> HELLO WORLD

Writer.upcase "Hello World"
# -> so.rb:16:in `<main>': private method `upcase' called for Writer:Module (NoMethodError)

8
这应该是被接受的答案。在我看来,它干净流畅且符合惯用语。 - Martin Nyaga
@MartinNyaga 这种方法的缺点是没有包括 include Writer 选项! - Ulysse BN

29
您可以使用“included”方法在混入模块时执行高级操作。 我认为这大致做到了您想要的:
module Foo
  def self.included(base)
    class << base 
      def public_method
        puts "public method"
      end
      def call_private
        private_method
      end
      private
      def private_method
        puts "private"
      end
    end
  end
end

class Bar
  include Foo
end

Bar.public_method

begin
  Bar.private_method
rescue
  puts "couldn't call private method"
end

Bar.call_private

5
聪明。所以这是可能的,但可能不值得。 - Daniel Beardsley
工作得很好。我使用了 included do |base| [...] end 而不是 def。 - Crystark
6
如果我没记错的话,这种语法只存在于扩展ActiveSupport::Concern的模块中,也就是说它是Rails的一个特性。 - Vaz

11

不幸的是,private 只适用于实例方法。获取类中私有的“静态”方法的通常方法是执行以下操作:

class << self
  private

  def foo()
   ....
  end
end

诚然,我没有在模块中尝试过这样做。


7
这不是真的。你可以拥有私有类方法和私有模块方法。 - mikeycgto
你可以拥有私有类方法,但仅仅这样做并不能使.foo成为一个私有类方法:"private; def self.foo()" - Ari
@mikeycgto,能否详细说明私有类方法和私有模块方法之间的区别?因为我认为它们是一样的。请注意,privateprivate_class_method都归属于Module而不是Class。顺便说一下,这段代码是可行的,也是使用private_class_method的替代方案。 - konsolebox

4

除非您通过方法参数显式传递数据,否则此方法将不允许与私有方法共享数据。

module Thing
  extend self

  def pub
    puts priv(123)
  end

  private
  
  def priv(value)
    puts "Private method with value #{value}"
  end
end

Thing.pub
# "Private method with value 123"

Thing.priv
# NoMethodError (private method `priv' called for Thing:Module)

3
一种好的方式是这样的:

module MyModule
  class << self
    def public_method
      # you may call the private method here
      tmp = private_method
      :public
    end

    private def private_method
      :private
    end
  end
end

# calling from outside the module
puts MyModule::public_method

1

在类变量/常量中存储方法作为lambda表达式怎么样?

module MyModule
  @@my_secret_method = lambda {
    # ...
  }
  # ...
end

测试用:
更新:6年后的巨大更新展示了更清晰的声明私有方法d

module A
  @@L = lambda{ "@@L" }
  def self.a ; @@L[] ; end
  def self.b ; a ; end

  class << self
    def c ; @@L[] ; end
    private
    def d ; @@L[] ; end
  end
  def self.e ; c ; end
  def self.f ; self.c ; end
  def self.g ; d ; end
  def self.h ; self.d ; end

  private
  def self.i ; @@L[] ; end
  class << self
    def j ; @@L[] ; end
  end

  public
  def self.k ; i ; end
  def self.l ; self.i ; end
  def self.m ; j ; end
  def self.n ; self.j ; end
end

for expr in %w{ A.a A.b A.c A.d A.e A.f A.g A.h A.i A.j A.k A.l A.m A.n }
  puts "#{expr} => #{begin ; eval expr ; rescue => e ; e ; end}"
end

这里我们看到:

A.a => @@L
A.b => @@L
A.c => @@L
A.d => private method `d' called for A:Module
A.e => @@L
A.f => @@L
A.g => @@L
A.h => private method `d' called for A:Module
A.i => @@L
A.j => @@L
A.k => @@L
A.l => @@L
A.m => @@L
A.n => @@L

1) @@L 无法从外部访问,但几乎可以从任何地方访问
2) class << self ; private ; def 成功使方法d无法从内外部访问,但没有使用self.时会出现奇怪的情况
3) private ; self.private ; class << self 不能将方法设为私有 -- 它们既可以使用self.也可以不使用。


Lambda表达式和方法完全不同。Lambda表达式的类型是“Proc”,而方法的类型是“Method”。 - Michael Dorst
1
全局变量不好。 - achempion
@achempion,你在哪里看到它们? - Nakilon
@Nakilon,抱歉,如果您希望我取消我的投票,请编辑您的答案。 - achempion

0

下面是一个解决方案,可以在单个模块内嵌套多个类,并使用 extend 来调用模块上的私有方法,从任何一个嵌套类中都可以访问:

module SomeModule

  class ClassThatDoesNotExtendTheModule
    class << self
      def random_class_method
        private_class_on_module
      end
    end
  end

  class ClassThatDoesExtendTheModule
    extend SomeModule
  
    class << self
      def random_class_method
        private_class_on_module
      end
    end
  end

  class AnotherClassThatDoesExtendTheModule
    extend SomeModule
  
    class << self
      def random_class_method
        private_class_on_module
      end
    end
  end

  private

  def private_class_on_module
    puts 'some private class was called'
  end
  
end

一些输出以展示解决方案的运行情况:

> SomeModule::ClassThatDoesNotExtendTheModule.random_class_method

NameError: undefined local variable or method `private_class_on_module' for SomeModule::ClassThatDoesNotExtendTheModule:Class


> SomeModule::ClassThatDoesExtendTheModule.random_class_method

some private class was called


> SomeModule::ClassThatDoesExtendTheModule.private_class_on_module

NoMethodError: private method `private_class_on_module' called for SomeModule::ClassThatDoesExtendTheModule:Class


> SomeModule::AnotherClassThatDoesExtendTheModule.random_class_method

some private class was called


> SomeModule::AnotherClassThatDoesExtendTheModule.random_class_method

NoMethodError: private method `private_class_on_module' called for SomeModule::AnotherClassThatDoesExtendTheModule:Class

0

创建私有模块或类

常量永远不会是私有的。但是,可以创建一个模块或类而不将其分配给常量。

因此,与:private_class_method的替代方法是创建一个私有模块或类,并在其上定义公共方法。

module PublicModule
  def self.do_stuff(input)
    @private_implementation.do_stuff(input)
  end

  @private_implementation = Module.new do
    def self.do_stuff(input)
      input.upcase # or call other methods on module
    end
  end
end

使用方法:

PublicModule.do_stuff("whatever") # => "WHATEVER"

请查看 Module.newClass.new 的文档。


我真的很喜欢这种方法。但是似乎无法在方法定义中删除.self,将其包含在另一个类中,并将它们用作包含类的实例方法。你知道有没有办法让它工作吗? - Shiyason

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接