【问题标题】:Private module methods in RubyRuby 中的私有模块方法
【发布时间】:2010-09-24 01:09:03
【问题描述】:

我有一个两部分的问题

最佳实践

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

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

  • 模块带有静态(ruby 中的“模块”)方法
  • 与静态方法
  • Mixin 模块,用于包含在数据结构中
  • 重构算法中修改该数据结构(非常小)的部分,并使其成为调用算法模块的静态方法的 mixin

技术部分

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

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

里面的private好像没有任何作用,我还是可以毫无问题地拨打Thing.priv

【问题讨论】:

  • 仅供参考,ruby 中没有“静态”方法,它们被称为类实例方法
  • 一个旧评论,但由于它有四个赞成票,我必须指出没有“类实例方法”这样的东西。 “类方法”是正确的术语。
  • private 只影响实例方法,不影响类方法。改用private_class_methodmodule Thing; def self.pub; end; private_class_method :pub; end
  • @micapam Ruby 中确实存在类实例方法,它们与类方法不同。

标签: ruby private-methods access-specifier


【解决方案1】:

我认为最好的方法(主要是现有库的编写方式)是在模块中创建一个处理所有逻辑的类,并且模块只是提供一种方便的方法,例如

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

【讨论】:

  • 红宝石新手在这里。在此示例中,Translator 类是否作为模块公共接口的一部分公开? 'perform' 方法能否将其访问权限限制为 GTranslate?
  • @rshepherd perform不是这里应该是私有的方法,私有方法是Translator类中的私有方法(@ucron的例子没有,什么是非常不幸)。 GTranslate.translate 只是GTranslate::Translator#perform 的一种方便的方法,如果可能的话,隐藏它并没有真正的好处。
  • 我不确定在这里开设课程能取得什么成就。如果目标是拥有一个私有模块方法,那么这不符合目标。因为您可以通过调用 GTranslate::Translator.new.perform 从模块外部访问“执行”方法。换句话说,它不是私有的。
  • @jschorr 我认为 Op 和这个答案打算创建一个私有类或模块方法,而不是实例方法。此外,这不会使任何实例方法成为私有的,因为 self.translate 声明了一个类/模块方法。
  • GTranslate::Translator.new.perform(text) — 复杂,但不私密!
【解决方案2】:

这里有一个解决方案,您可以通过使用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

【讨论】:

    【解决方案3】:

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

    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)
    

    【讨论】:

      【解决方案4】:

      将方法存储为类变量/常量中的 lambda 有何意义?

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

      用于测试:
      UPD: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 &lt;&lt; self ; private ; def 成功地使 d 方法无法使用 self. 从外部和内部访问,但不是没有它 - 这很奇怪
      3) private ; self.private ; class &lt;&lt; self 不会将方法设为私有——无论有无self.,它们都可以访问

      【讨论】:

      • lambdas 与方法完全不同。 lambda 的类型为 Proc,而方法的类型为 Method
      • 全局变量不好
      • @achemion,你在哪里看到的?
      • @Nakilon 我很抱歉,如果你想取消我的投票,请编辑你的答案
      【解决方案5】:

      创建一个私有模块或类

      常量永远不是私有的。但是,可以在不将其分配给常量的情况下创建模块或类。

      所以: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,将其包含在另一个类中,并将它们用作包含类的instance_methods。不知道有没有办法让它工作?
      【解决方案6】:
      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)
      

      【讨论】:

      • 这应该是公认的答案。在我看来,干净且惯用。
      • @MartinNyaga 这有没有include Writer 选项的缺点!
      【解决方案7】:

      一个不错的方法是这样的

      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
      

      【讨论】:

        【解决方案8】:

        还有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
        • 这个答案以及@JCooper's answer 是真正的解决方案。 @MarnenLaibow-Koser 没有。您可以以更多分组和缩进为代价考虑其他答案。它实际上可能是某些人的首选解决方案。 (回复仅供参考。)
        【解决方案9】:

        当一个模块混入时,你可以使用“包含”方法来做一些花哨的事情。这就是你想要的我想的:

        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
        

        【讨论】:

        • 这很聪明。所以这是可能的,但可能不值得。
        • 效果很好。我用included do |base| [...] end而不是def
        • @Crystark:如果我没记错的话,这种语法只存在于扩展 ActiveSupport::Concern 的模块上。即这是一个轨道的事情。
        【解决方案10】:

        不幸的是,private 仅适用于实例方法。在类中获取私有“静态”方法的一般方法是:

        class << self
          private
        
          def foo()
           ....
          end
        end
        

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

        【讨论】:

        • 这不是真的。你可以有私有类方法和私有模块方法。
        • 你可以有私有类方法,但是这样做不会使.foo成为私有类方法:"private; def self.foo()"
        • @mikeycgto 想详细说明私有类方法和私有模块方法之间的区别吗?因为我认为他们是一样的。请注意,privateprivate_class_method 均归 Module 而非 Class 所有。顺便说一句,此代码有效,它是使用 private_class_method 的替代方法。
        猜你喜欢
        • 2011-03-30
        • 2015-12-08
        • 2013-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-16
        • 2021-10-08
        • 2012-05-19
        相关资源
        最近更新 更多